@ultimat3/http 23.0.0 → 25.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CLAUDE.md +75 -31
  2. package/README.md +89 -26
  3. package/package.json +9 -6
  4. package/src/app-config.ts +3 -1
  5. package/src/auth-redirect.ts +56 -17
  6. package/src/bearer-mount.ts +81 -26
  7. package/src/cache-policy.ts +10 -2
  8. package/src/config.ts +62 -32
  9. package/src/context.ts +11 -11
  10. package/src/correlation.ts +2 -2
  11. package/src/cors.ts +3 -2
  12. package/src/csrf.ts +17 -8
  13. package/src/deadline.ts +10 -8
  14. package/src/error-facts.ts +18 -2
  15. package/src/error-map-http.ts +94 -0
  16. package/src/error-map-tier-0.ts +62 -0
  17. package/src/error-map-tier-1.ts +73 -0
  18. package/src/error-map-tier-2.ts +120 -0
  19. package/src/error-map-tier-3.ts +100 -0
  20. package/src/error-map-tier-4.ts +116 -0
  21. package/src/error-map.ts +27 -474
  22. package/src/error-page.ts +3 -4
  23. package/src/error-titles.ts +93 -0
  24. package/src/errors.ts +46 -96
  25. package/src/forwarded.ts +36 -10
  26. package/src/health-disclosure.ts +66 -0
  27. package/src/hooks.ts +6 -5
  28. package/src/html-render.ts +3 -17
  29. package/src/index.ts +36 -27
  30. package/src/locale-prefix.ts +5 -1
  31. package/src/locale.ts +0 -27
  32. package/src/navigation.ts +26 -1
  33. package/src/overlay.ts +3 -1
  34. package/src/peer-identity.ts +10 -6
  35. package/src/pipeline.ts +32 -13
  36. package/src/preferences.ts +1 -1
  37. package/src/problem-meta.ts +7 -21
  38. package/src/rate-limit-buckets.ts +26 -2
  39. package/src/rate-limit-errors.ts +36 -10
  40. package/src/rate-limit-headers.ts +40 -0
  41. package/src/rate-limit-installed.ts +79 -0
  42. package/src/rate-limit-peek.ts +32 -0
  43. package/src/rate-limit-postgres.ts +30 -21
  44. package/src/rate-limit-stage.ts +132 -0
  45. package/src/rate-limit.ts +40 -15
  46. package/src/request-facts.ts +44 -0
  47. package/src/request.ts +12 -5
  48. package/src/response.ts +10 -2
  49. package/src/route-cache.ts +1 -1
  50. package/src/router.ts +36 -8
  51. package/src/server.ts +101 -30
  52. package/src/set-cookie.ts +60 -0
  53. package/src/stages.ts +50 -54
  54. package/src/validate.ts +2 -2
@@ -0,0 +1,116 @@
1
+ // One slice of the status table `error-map.ts` composes: tier 4 — `render`, `pwa`, `mcp`, `ai`, `manifest`, `mail`, `ui`, `notify`.
2
+ // A row here is a code owned by a tier-4 package; `bun run new-error-code` appends to the slice its
3
+ // `--package` belongs to, so no one file grows with every code the framework adds.
4
+
5
+ export const TIER_4_ERROR_STATUS = {
6
+ // @ultimat3/notify — five 500s and one 502, and the split is who failed.
7
+ //
8
+ // The five are the app's own declaration: a notifier with no channels, one channel named twice,
9
+ // a digest window on a bulk channel, a store nothing installed, and a fan-out past the per-run
10
+ // ceiling. Every `fix:` on those five names a code edit or a boot call, so nothing a caller
11
+ // sends changes any of them — `X_NOTIFY_FANOUT_TOO_WIDE` is the only one a request can even
12
+ // INFLUENCE (an action that notifies a whole org), and the repair is still `bulkChannel()` or a
13
+ // paged `backfill()`, never the request.
14
+ X_NOTIFY_CHANNELS_EMPTY: 500,
15
+ X_NOTIFY_CHANNEL_DUPLICATE: 500,
16
+ X_NOTIFY_FANOUT_TOO_WIDE: 500,
17
+ X_NOTIFY_STORE_MISSING: 500,
18
+ X_NOTIFY_DIGEST_UNSUPPORTED: 500,
19
+ // 502, and it is the one row on this table that answers for somebody else's server. This code
20
+ // WRAPS a provider rejection — `NotifyDeliveryFailedError` takes the caught value and renders it
21
+ // — so the thing that failed is the channel's upstream, not this process. It is thrown inside a
22
+ // job step today (`x jobs show <notifier> --json` is its own `fix:`), so nothing reaches a
23
+ // request and the number is unobservable either way; the row is chosen for the day that stops
24
+ // being true, and the asymmetry decides it. A wrong 502 costs nothing. A wrong 500 pages the
25
+ // on-call for an email provider's outage, because `stages.ts` reports every `status >= 500` to
26
+ // the error monitor — which is the failure this whole table exists to stop.
27
+ X_NOTIFY_DELIVERY_FAILED: 502,
28
+ // @ultimat3/ui — a form control whose `name` is not a usable field path. The owning slice argued
29
+ // for NO ROW, on the grounds that this is a render-time developer error that can never reach
30
+ // HTTP, and the argument is right about the code and wrong about the table.
31
+ //
32
+ // `scripts/error-map-backlog.ts` is the only "no row" this table has, and its own header says
33
+ // what an entry there means: "NOT a claim that the code can never cross HTTP … a claim that
34
+ // nobody has decided yet", with the ratchet promising only that the undecided set never grows.
35
+ // This code HAS been decided, so a pin would record the opposite of what is known and grow the
36
+ // one list that may not grow.
37
+ //
38
+ // So it takes the answer every other decided-and-unreachable code takes — `X_CORS_CONFIG_INVALID`,
39
+ // `X_ACTION_JOB_UNBRIDGED`, `X_RATE_LIMIT_NOT_SHARED`. The row is NOT a claim that it reaches a
40
+ // request. A code with no row already answers 500 (`DEFAULT_STATUS`); the row changes nothing at
41
+ // runtime and makes that answer a reviewed one instead of an accident, which is the whole reason
42
+ // this table is closed.
43
+ X_UI_FORM_PATH_INVALID: 500,
44
+ // @ultimat3/ui — `defineTheme()` refused a brand palette whose RESOLVED channels put a pairing
45
+ // below WCAG 2.2 AA. Declaration-time in the ordinary case, and it takes a row rather than a
46
+ // backlog entry for the one case that is not: an app that renders a per-tenant brand stylesheet
47
+ // during a request throws this ON that request. The author's fault and never the caller's, so
48
+ // 500 is the honest class, and a DECLARED 500 rather than an unclassified one — `toProblem`
49
+ // blanks the cause of an unclassified failure outside dev, which would hide the one thing that
50
+ // makes this error actionable: the role and the measured ratio.
51
+ X_UI_CONTRAST_INSUFFICIENT: 500,
52
+ // @ultimat3/ui — `<QrCode>` was handed a value past the encoder's version-3 ceiling. The value
53
+ // is the app's own (a short URL it minted), never the caller's, so 500 is the honest class —
54
+ // and a declared 500 for the contrast code's reason: `toProblem` blanks an unclassified
55
+ // failure's cause outside dev, and the cause is where the byte count and the ceiling are.
56
+ X_UI_QR_CAPACITY: 500,
57
+ // @ultimat3/render — an island handed props it cannot carry: an undeclared key, a value that is
58
+ // not JSON, or a bag over `ISLAND_PROPS_MAX_BYTES`. The author's fault and never the caller's,
59
+ // so 500 is the honest class — and it HAS to be a declared 500. Without a row the code was an
60
+ // unclassified failure, and `toProblem` blanks the cause of one of those outside dev
61
+ // (`isUnclassifiedFailure`): a 34-row catalog over the cap took a page down with a problem
62
+ // document that said "the details are in this process's logs" about an error whose whole
63
+ // value is the sentence naming the prop and its bytes. Measured on ai-maxxing, 2026-09-05.
64
+ X_ISLAND_PROPS_INVALID: 500,
65
+ // A loader answered `withStatus` with a 3xx, or a status outside 200–599. Raised INSIDE the
66
+ // request that ran the loader, so it needs a row for the same reason the line above does: an
67
+ // unclassified 500 blanks the sentence naming the status and the `redirect()` to use instead.
68
+ X_ROUTE_STATUS_INVALID: 500,
69
+ // `asset('assets/…')` named a file that is not on disk. The static build refuses it, but an `ssr`
70
+ // page calls `asset()` INSIDE the request it renders, so it reaches a caller there — a deploy
71
+ // defect, never the visitor's, hence 500, declared so the cause naming the missing file survives.
72
+ X_ASSET_MISSING: 500,
73
+ // @ultimat3/mail
74
+ // The deployment configured no transport. It reaches a caller only through an inline
75
+ // `send(…, { sync: true })` inside a request; the queued path dead-letters instead. A server-side
76
+ // configuration fault either way, so 500 and never a 4xx — nothing the caller sent is wrong, and
77
+ // this is exactly the condition somebody should be paged for.
78
+ X_MAIL_CREDENTIAL_MISSING: 500,
79
+ // @ultimat3/mcp — the one MCP code that is answered on a REQUEST rather than inside a JSON-RPC
80
+ // envelope, which is what the rest of that package's backlog group says about the others: the
81
+ // transport refused before dispatch, so there is no call to answer. 429 because
82
+ // `mcpHttpRoute` already builds that response by hand (`transport-http.ts`'s `throttled`), with
83
+ // `retry-after` beside it. The row is what keeps the two surfaces from disagreeing the day the
84
+ // MCP host is mounted inside this pipeline — a code that renders 429 on one and 500 on the other
85
+ // is exactly the split this table exists to prevent.
86
+ X_MCP_RATE_LIMITED: 429,
87
+ // The second MCP code answered on a request before dispatch, and 413 for the same reason the row
88
+ // above is 429: `transport-http.ts` already answers it with that status by hand.
89
+ X_MCP_BODY_TOO_LARGE: 413,
90
+ // @ultimat3/ai — a vector store was read with no tenant bound inside an org request
91
+ X_VECTOR_UNSCOPED: 500,
92
+ // @ultimat3/ai — an image or document block the role, model or wire format cannot take
93
+ X_AI_CONTENT_UNSUPPORTED: 422,
94
+ // @ultimat3/mcp — a gated MCP tool call waits for a person to approve it
95
+ X_MCP_CONFIRMATION_PENDING: 409,
96
+ // @ultimat3/mcp — an MCP confirmation expired before it was used or decided
97
+ X_MCP_CONFIRMATION_EXPIRED: 410,
98
+ // @ultimat3/mcp — a person rejected this MCP tool call
99
+ X_MCP_CONFIRMATION_REJECTED: 403,
100
+ // @ultimat3/mcp — an MCP confirmation was already decided
101
+ X_MCP_CONFIRMATION_DECIDED: 409,
102
+ // @ultimat3/mcp — no MCP confirmation has that id
103
+ X_MCP_CONFIRMATION_UNKNOWN: 404,
104
+ // @ultimat3/mcp — identical concurrent MCP calls kept racing one confirmation
105
+ X_MCP_CONFIRMATION_CONTESTED: 503,
106
+ // @ultimat3/mail — a delivery notification is not signed by its provider
107
+ X_MAIL_EVENT_UNVERIFIED: 401,
108
+ // @ultimat3/mail — a delivery notification is not a shape this receiver reads
109
+ X_MAIL_EVENT_INVALID: 400,
110
+ // @ultimat3/mail — an SNS endpoint the receiver needed did not answer
111
+ X_MAIL_EVENT_PROVIDER_UNREACHABLE: 503,
112
+ // @ultimat3/mcp — an MCP confirmation approval did not carry the arguments the agent sent
113
+ X_MCP_CONFIRMATION_ARGUMENTS_MISMATCH: 409,
114
+ // @ultimat3/ai — a model call named no model
115
+ X_AI_MODEL_UNRESOLVED: 500,
116
+ } satisfies Readonly<Record<string, number>>;
package/src/error-map.ts CHANGED
@@ -1,8 +1,24 @@
1
- // The one place a framework error code becomes an HTTP status. A table, not a
2
- // switch chain: adding a code elsewhere in the framework means adding a row here,
3
- // and a missing row is a loud 500 rather than a silently wrong 200.
4
- // Rendering a throwable for a reader is `error-facts.ts`; reading this table, and the app's own
5
- // half of it, is `error-status.ts`. This file is the table.
1
+ // The one place a framework error code becomes an HTTP status — a table, so a missing row is a
2
+ // loud 500 rather than a silently wrong 200. The rows live in one slice per tier
3
+ // (`error-map-tier-<n>.ts`, `error-map-http.ts` for this package's own) and are composed here;
4
+ // `scripts/new-error-code.ts` appends to the slice. Reading the table is `error-status.ts`.
5
+
6
+ import { HTTP_ERROR_STATUS } from './error-map-http';
7
+ import { TIER_0_ERROR_STATUS } from './error-map-tier-0';
8
+ import { TIER_1_ERROR_STATUS } from './error-map-tier-1';
9
+ import { TIER_2_ERROR_STATUS } from './error-map-tier-2';
10
+ import { TIER_3_ERROR_STATUS } from './error-map-tier-3';
11
+ import { TIER_4_ERROR_STATUS } from './error-map-tier-4';
12
+
13
+ /** Every slice, in composition order — `error-map.test.ts` holds them disjoint. */
14
+ export const ERROR_STATUS_SLICES = Object.freeze([
15
+ HTTP_ERROR_STATUS,
16
+ TIER_0_ERROR_STATUS,
17
+ TIER_1_ERROR_STATUS,
18
+ TIER_2_ERROR_STATUS,
19
+ TIER_3_ERROR_STATUS,
20
+ TIER_4_ERROR_STATUS,
21
+ ] as const);
6
22
 
7
23
  /**
8
24
  * code -> status. Codes owned by other packages are listed here on purpose: HTTP
@@ -10,475 +26,12 @@
10
26
  * ever hardcode one.
11
27
  */
12
28
  export const ERROR_STATUS = {
13
- // @ultimat3/http
14
- X_ROUTE_NOT_FOUND: 404,
15
- X_METHOD_NOT_ALLOWED: 405,
16
- // The request line itself is unreadable, so there is nothing to route: 400, and never a 500 —
17
- // a malformed escape is the caller's typo, not this server's defect.
18
- X_PATH_INVALID: 400,
19
- X_BODY_INVALID: 422,
20
- X_UNAUTHENTICATED: 401,
21
- X_FORBIDDEN: 403,
22
- X_RATE_LIMITED: 429,
23
- X_BUILD_SKEW: 409,
24
- X_ROUTE_CONFLICT: 500,
25
- X_SERVER_NOT_STARTED: 500,
26
- X_PIPELINE_NO_RESPONSE: 500,
27
- // The request was answered and the answer could not be finished: the caller gets nothing usable
28
- // either way, so this is the server's failure, never the caller's.
29
- X_PIPELINE_FINALIZE_FAILED: 500,
30
- // Both are wiring bugs, never a caller's mistake: reading a cookie where no request exists,
31
- // and declaring a status the framework already owns. 500 is the honest answer to either.
32
- X_NO_REQUEST: 500,
33
- X_ERROR_STATUS_INVALID: 500,
34
- X_PROBLEM_META_INVALID: 500,
35
- // A `hive()` whose `split()` returned no members. The caller cannot fix it by sending
36
- // different input — the guard belongs in the app, either by returning at least one member
37
- // or by skipping the hive when the source is empty — so it is the server's bug, not theirs.
38
- X_HIVE_EMPTY: 500,
39
- // Thrown while `app.config.ts` resolves, so no request is ever answered with it — the row exists
40
- // because a code with no status is a 500 anyway and this table is the closed one.
41
- X_CORS_CONFIG_INVALID: 500,
42
- // Core's code, borrowed by `defineHttpConfig` for a numeric knob that is not a count. Same
43
- // construction-time shelf as the row above, and it needs a row for the same reason: this table
44
- // is closed over every code the package can throw, borrowed ones included.
45
- X_CONFIG_INVALID: 500,
46
- X_CSP_DIRECTIVE_INVALID: 500,
47
- // Thrown while the server is being constructed, so no request is ever answered with it either.
48
- // The row exists because this table is the closed one: a code missing from it is a 500 anyway,
49
- // and a code the framework owns must never fall through to the app's table.
50
- X_RATE_LIMIT_NOT_SHARED: 500,
51
- // Same construction-time class as the row above: a route and the config declare one bucket
52
- // differently, and the process refuses to start rather than pick.
53
- X_RATE_LIMIT_BUCKET_CONFLICT: 500,
54
- // Construction time as well: the limiter installed cannot enforce a bucket a route declares.
55
- X_RATE_LIMIT_BUCKET_UNBOUND: 500,
56
- // `defineHttpConfig` time, all three: a declaration the deployment owes and did not make, or
57
- // made against a bucket nothing declares.
58
- X_RATE_LIMIT_SCOPE_UNSET: 500,
59
- X_RATE_LIMIT_TENANT_BUCKET_UNKNOWN: 500,
60
- X_TRUST_PROXY_UNSET: 500,
61
- // Raised by `toBucket` while a route or an action is being projected, never on the request.
62
- X_RATE_LIMIT_INVALID: 500,
63
- // The shared store did not answer, so nothing decided. An operator's fault, never the caller's.
64
- X_RATE_LIMIT_STORE_UNAVAILABLE: 500,
65
- // The two the `admit` stage answers with, and the only 503s the pipeline produces. Both carry
66
- // `retry-after`: a shed request that does not say when to come back is a request that comes
67
- // back immediately, which is the load it was shed to avoid.
68
- X_DRAINING: 503,
69
- X_OVERLOADED: 503,
70
- // Core's flight gate refusing past `maxQueued` is the same answer one tier down: it carries
71
- // `retryAfterSeconds` in `meta`, which `stages.ts` renders onto `Retry-After`, so a caller that
72
- // already handles a shed request needs no second branch for a refused one.
73
- X_FLIGHT_GATE_OVERLOADED: 503,
74
- // 403 and never 401: the caller IS authenticated — that is what makes the forged write work —
75
- // so a 401 would send a signed-in user to a sign-in page they are already past.
76
- X_CSRF_BLOCKED: 403,
77
- // 401 for both, and never 400: an inbound webhook is well formed and carries a CREDENTIAL — a
78
- // timestamped hmac over its own bytes — so what failed is authentication, not the request. Never
79
- // 403 either, which means an authenticated caller was refused, and there is no authenticated
80
- // caller here. Two codes rather than one because the repairs differ and a sender's dashboard
81
- // shows the status: `INVALID` is the wrong secret or a rewritten body, `STALE` is a skewed clock
82
- // or a delivery being replayed off a capture. Neither triggers `signInRedirect`, which keys on
83
- // `X_UNAUTHENTICATED` alone — a webhook sender is not a browser and has no session to go get.
84
- X_WEBHOOK_SIGNATURE_INVALID: 401,
85
- X_WEBHOOK_SIGNATURE_STALE: 401,
86
- // Boot refusals of the app's HTTP declaration (a bearer mount, an action's pinned path or the
87
- // app's path style or a path derived before it, the openapi block, the MCP oauth block or an
88
- // idempotent tool shadowing its key argument, a page's `post` or `navigation`) — never a request's
89
- // answer; 500 if one ever escaped into a response (`x dev` re-evaluating an edited route module):
90
- // the deployment is wrong, the caller is not.
91
- X_BEARER_MOUNT_INVALID: 500,
92
- X_ACTION_HTTP_PATH_INVALID: 500,
93
- X_ACTION_PATH_STYLE_INVALID: 500,
94
- X_ACTION_PATH_DERIVED_EARLY: 500,
95
- X_OPENAPI_CONFIG_INVALID: 500,
96
- X_MCP_OAUTH_INVALID: 500,
97
- X_MCP_PATH_DUPLICATE: 500,
98
- X_MCP_IDEMPOTENCY_KEY_SHADOWED: 500,
99
- X_ROUTE_POST_INVALID: 500,
100
- X_ROUTE_NAVIGATION_INVALID: 500,
101
- // @ultimat3/action — the code every primitive throws when the CALLER's input fails the schema
102
- // the primitive declared. 400 because that is what the published OpenAPI operation promises for
103
- // it, and because a missing row made a typo'd uuid a 500: the caller was told the server broke,
104
- // and the `error-map` stage reported the caller's mistake to the on-call monitor.
105
- X_INPUT_INVALID: 400,
106
- // The one way a SERVER answers this code: a POST to the path an action has under a path style
107
- // this app does not serve (`hooks.explainMiss`). 404 because it is still a miss — nothing is
108
- // routed there and nothing ran, so the status a proxy or a monitor counts is the one the plain
109
- // `X_ROUTE_NOT_FOUND` had; the code and the `fix:` are what changed. Every other raise of it is
110
- // client-side or a contract test, and never reaches this table.
111
- X_CONTRACT_DRIFT: 404,
112
- // A retried `Idempotency-Key` naming a different payload, or one still in flight. 409 because
113
- // that is what the action's own OpenAPI operation publishes for it — the runtime answered 500
114
- // while the document promised 409, and a client written against the spec read the framework
115
- // working exactly as designed as an outage.
116
- X_IDEMPOTENCY_CONFLICT: 409,
117
- // A blank or over-long `Idempotency-Key` HEADER, refused before the handler runs. 400 and not
118
- // the 422 a body gets: what failed is a parameter the OpenAPI operation publishes a `maxLength`
119
- // for, which is the same thing `X_INPUT_INVALID` is 400 for.
120
- X_IDEMPOTENCY_KEY_INVALID: 400,
121
- // 500, and deliberately not the 409 above. `IdempotencyReplayedFailureError` re-throws the FIRST
122
- // attempt's own code whenever the store recorded one, so this literal code is reached only when
123
- // that attempt failed carrying no code at all: an unclassified throw whose commit state nobody
124
- // knows. That is the server's to explain, and it is worth reporting.
125
- X_IDEMPOTENCY_REPLAYED_FAILURE: 500,
126
- // Same shape as the line above and 500 for the same reason: the store holds a record this
127
- // build cannot turn into a result. Deliberately NOT 503 — a rolling deploy is the usual
128
- // cause, so a retry may well reach a newer pod and succeed, but this code carries no
129
- // `retry-after` and the two 503s above are the only ones that do. Telling a caller to come
130
- // back without saying when is the load-shedding mistake, one layer up.
131
- X_IDEMPOTENCY_STATUS_UNKNOWN: 500,
132
- // @ultimat3/auth — every one of these is reachable from a request: the OAuth route descriptors
133
- // are mounted by the app, and `authenticate` throws the session codes inside the pipeline. Without
134
- // a row each fell to 500, so a user pressing Cancel on a consent screen paged the on-call and a
135
- // provider this app never enabled read as an outage. `packages/auth/src/oauth-route.ts` answers
136
- // from this table's values when its descriptors are driven OUTSIDE a pipeline; the pin that keeps
137
- // the two identical is `scripts/oauth-route-status.test.ts`, since auth is this tier and cannot
138
- // import this package.
139
- X_SESSION_EXPIRED: 401,
140
- X_MFA_REQUIRED: 401,
141
- X_ACCOUNT_LOCKED: 429,
142
- X_API_KEY_INVALID: 401,
143
- X_OAUTH_STATE_INVALID: 400,
144
- X_OAUTH_TOKEN_INVALID: 400,
145
- X_OAUTH_PROVIDER_UNKNOWN: 404,
146
- X_OAUTH_DENIED: 403,
147
- // 502, not 500: the conversation that failed is with the provider's server, and the on-call
148
- // question "is it us or them?" is the one a status is read for.
149
- X_OAUTH_EXCHANGE_FAILED: 502,
150
- // 422 and not 400: the body parsed, the field is a string, and a policy rejected its CONTENT —
151
- // the same class as `X_BODY_INVALID` and `X_INVARIANT_VIOLATED` above. Unmapped, a visitor
152
- // choosing "password" at a signup form was reported to the on-call monitor as a server fault.
153
- X_PASSWORD_WEAK: 422,
154
- // 422 for the same reason as the row above, and deliberately not 500: `enrolTotp` throws it for a
155
- // secret the CALLER supplied — an import from another MFA system, or a value off a form — and
156
- // what failed is that value's content, not the server. `verifyTotp` never throws it (an
157
- // unreadable stored secret is a non-verdict there, the rule `verifyAgainst` follows for a hash
158
- // Bun cannot read), so a login checking a broken row cannot reach this status at all.
159
- X_MFA_SECRET_INVALID: 422,
160
- // @ultimat3/entity
161
- X_NOT_FOUND: 404,
162
- X_ENTITY_DUPLICATE: 409,
163
- X_INVARIANT_VIOLATED: 422,
164
- X_TENANCY_UNSCOPED: 500,
165
- X_DB_DRIFT: 500,
166
- // Both are the server's verdict on a statement the app wrote, which is the app's fault and not
167
- // the caller's: a request cannot repair a missing migration or a wrong column list.
168
- X_DB_SCHEMA_STALE: 500,
169
- X_DB_STATEMENT_FAILED: 500,
170
- // Logged at boot, never thrown into a request; the row exists so the table stays closed.
171
- X_MCP_APP_UNMOUNTED: 500,
172
- // The three tenancy refusals, all 403, and all deliberately NOT the 404 `X_STORAGE_ORG_MISMATCH`
173
- // takes: that one answers 404 because a 403 on a KEY the caller supplied confirms the key exists.
174
- // These three name no resource and read no row — the comparison is the actor against an argument
175
- // (`X_TENANCY_ACTOR_MISMATCH`), the actor against nothing at all (`X_TENANCY_ACTOR_ORG_REQUIRED`),
176
- // or the actor's scopes at `crossTenant()` (`X_TENANCY_CROSS_DENIED`) — so the answer is the same
177
- // whether or not the other tenant's row exists, and a 404 would buy no secrecy for the lie.
178
- X_TENANCY_ACTOR_MISMATCH: 403,
179
- // 403 and never 401, for the reason `X_CSRF_BLOCKED` is one: the actor may be fully
180
- // authenticated and merely carry no org — a service actor minted without one — so a 401 sends a
181
- // signed-in caller to a sign-in page that cannot give them a tenant.
182
- X_TENANCY_ACTOR_ORG_REQUIRED: 403,
183
- X_TENANCY_CROSS_DENIED: 403,
184
- // The three aggregate refusals, all 500 and all deliberately NOT a 4xx, for the reason
185
- // `X_QUERY_NOT_PAGEABLE` below is one: nothing the caller sends changes the answer, and the fix
186
- // is an edit to the read itself. They earn ROWS rather than a pin in `scripts/error-map-backlog.ts`
187
- // because each carries an instruction the app's author needs and an unmapped 5xx is blanked —
188
- // `toProblem` replaces an undeclared code's cause with `INTERNAL_CAUSE`, so pinning them would
189
- // answer "the server failed while handling this request" for a fault whose own `fix:` names the
190
- // exact call to write instead. A row costs no extra page: `stages.ts` reports every `status >= 500`
191
- // either way.
192
- //
193
- // Reached with a request waiting, all three, which is why they are not in the backlog's entity
194
- // group ("misuse a handler's author makes") — the mixed-currency and the ±2^53 refusals are
195
- // decided by the ROWS, so a read that answered for two years starts failing on the day the data
196
- // crosses the line, and `approximateCount()` on a chain whose predicates came from the caller's
197
- // own optional filters is one query string away.
198
- X_AGGREGATE_UNSUPPORTED: 500,
199
- X_AGGREGATE_MIXED_CURRENCY: 500,
200
- X_APPROXIMATE_COUNT_FILTERED: 500,
201
- // The two search refusals, 500 for the reason the three above are: nothing the caller sends
202
- // changes either answer. An entity with no searchable column needs a `searchable()` on one, and
203
- // a driver that cannot answer a full-text match needs the Postgres one — both are edits to the
204
- // app, and both carry a `fix:` that an unmapped 5xx would blank (`toProblem` replaces an
205
- // undeclared code's cause with `INTERNAL_CAUSE`).
206
- X_SEARCH_UNDECLARED: 500,
207
- X_SEARCH_IN_MEMORY: 500,
208
- // The three state-machine refusals, and they are deliberately THREE statuses rather than one:
209
- // the machine says the transition does not exist, the row says it is somewhere else, or the
210
- // column says there is no machine at all — three different readers and three different repairs.
211
- //
212
- // 422 and not 400: the request is well formed and its schema passed. The transition the caller
213
- // named is not one this machine has, which is the same shape as `X_INVARIANT_VIOLATED` above and
214
- // takes its status. Refused before any statement opens a connection, so nothing was written.
215
- X_STATE_TRANSITION_ILLEGAL: 422,
216
- // 409, the lost update caught. The row moved between the read the caller decided on and the
217
- // write it asked for — nothing is wrong with either, and the repair is re-read and retry, which
218
- // is precisely what a 409 tells a client to do. A 422 would say "your request is unusable",
219
- // which is false: the identical request succeeds a moment later.
220
- X_STATE_CONFLICT: 409,
221
- // 500, the same shelf as `X_SEARCH_UNDECLARED`: a column with no machine is a declaration the
222
- // app has not written, and no request changes that.
223
- X_STATE_UNDECLARED: 500,
224
- // A sealed column named where the database would compare it, or in a view. Both are the app's
225
- // own declaration or call, never the caller's input — the same shelf as the two above.
226
- X_ENTITY_SEALED_PREDICATE: 500,
227
- X_ENTITY_SEALED_IN_VIEW: 500,
228
- // @ultimat3/db — the constraints a request trips, both 409. db's own `fix:` for the unique
229
- // violation says "answer 409, which is what a raced signup is", and `X_ENTITY_DUPLICATE` — the
230
- // same event one layer up — is 409 above; a foreign key rides with it because both halves of it
231
- // are a conflict with the state that is there (the parent is missing, or the child still points
232
- // at it), which 422 describes only for the insert.
233
- X_DB_UNIQUE_VIOLATION: 409,
234
- X_DB_FOREIGN_KEY_VIOLATION: 409,
235
- // @ultimat3/jobs — the ONE jobs code with a row here, and the reason the rest are pinned in
236
- // `scripts/error-map-backlog.ts` does not cover it. That pin says "a job runs with no socket
237
- // attached; `ROLE=worker` opens no HTTP port at all" — true of `X_JOB_TIMEOUT` and every other
238
- // worker-runtime code, and NOT true of a decode failure: `toJobRecord` runs wherever a row is
239
- // READ, which includes the admin dashboard's job panel and `x jobs show` served over HTTP.
240
- // 500, and it should page: a queue holding rows this build cannot read is an operator's
241
- // problem, and nothing the caller sent is wrong.
242
- X_JOB_ROW_STATUS_UNKNOWN: 500,
243
- // A requeue of a job that is still live — the admin panel's retry, `x jobs retry` over HTTP. The
244
- // job's STATE is wrong, not the server: 409, like `X_STORAGE_QUARANTINED`.
245
- X_JOB_NOT_REQUEUEABLE: 409,
246
- // Thrown by `job()` at declaration, while modules load; no request is answered with it. The row
247
- // exists for `X_ACTION_JOB_UNBRIDGED`'s reason: the table is closed, and it is a 500 anyway.
248
- X_JOB_DECLARATION_INVALID: 500,
249
- // Thrown by `registerJobs()` while the app's modules load, so no request is ever answered with
250
- // it either — the row exists for the reason `X_CORS_CONFIG_INVALID`'s does: this table is the
251
- // closed one, and a code with no row is a 500 anyway.
252
- X_ACTION_JOB_UNBRIDGED: 500,
253
- // Every `X_WEBHOOK_*` below is OUTBOUND and is thrown inside a worker: `ROLE=worker` opens no
254
- // HTTP port, so none of them ever answers a request. The rows exist for the reason
255
- // `X_ACTION_JOB_UNBRIDGED`'s does — this table is the closed one, and a code with no row is a
256
- // 500 anyway. The INBOUND pair (`X_WEBHOOK_SIGNATURE_*`, 401) is @ultimat3/http's and sits with
257
- // the rest of this package's codes above; these are the ones a delivery ends on.
258
- X_WEBHOOK_ENDPOINT_UNKNOWN: 500,
259
- X_WEBHOOK_ENDPOINT_INVALID: 500,
260
- X_WEBHOOK_ENDPOINT_DISABLED: 500,
261
- X_WEBHOOK_EVENT_UNKNOWN: 500,
262
- X_WEBHOOK_EVENT_INVALID: 500,
263
- X_WEBHOOK_DELIVERY_FAILED: 500,
264
- X_WEBHOOK_DELIVERY_THROTTLED: 500,
265
- X_WEBHOOK_DELIVERY_REJECTED: 500,
266
- // Same class again: an export pass runs in a worker, and both codes refuse the DECLARATION —
267
- // a `row()` that answers columns nobody declared, and a page too big to hold. Neither is
268
- // anything a caller sent.
269
- X_EXPORT_ROW_INVALID: 500,
270
- X_EXPORT_PART_TOO_LARGE: 500,
271
- // @ultimat3/notify — five 500s and one 502, and the split is who failed.
272
- //
273
- // The five are the app's own declaration: a notifier with no channels, one channel named twice,
274
- // a digest window on a bulk channel, a store nothing installed, and a fan-out past the per-run
275
- // ceiling. Every `fix:` on those five names a code edit or a boot call, so nothing a caller
276
- // sends changes any of them — `X_NOTIFY_FANOUT_TOO_WIDE` is the only one a request can even
277
- // INFLUENCE (an action that notifies a whole org), and the repair is still `bulkChannel()` or a
278
- // paged `backfill()`, never the request.
279
- X_NOTIFY_CHANNELS_EMPTY: 500,
280
- X_NOTIFY_CHANNEL_DUPLICATE: 500,
281
- X_NOTIFY_FANOUT_TOO_WIDE: 500,
282
- X_NOTIFY_STORE_MISSING: 500,
283
- X_NOTIFY_DIGEST_UNSUPPORTED: 500,
284
- // 502, and it is the one row on this table that answers for somebody else's server. This code
285
- // WRAPS a provider rejection — `NotifyDeliveryFailedError` takes the caught value and renders it
286
- // — so the thing that failed is the channel's upstream, not this process. It is thrown inside a
287
- // job step today (`x jobs show <notifier> --json` is its own `fix:`), so nothing reaches a
288
- // request and the number is unobservable either way; the row is chosen for the day that stops
289
- // being true, and the asymmetry decides it. A wrong 502 costs nothing. A wrong 500 pages the
290
- // on-call for an email provider's outage, because `stages.ts` reports every `status >= 500` to
291
- // the error monitor — which is the failure this whole table exists to stop.
292
- X_NOTIFY_DELIVERY_FAILED: 502,
293
- // @ultimat3/policy
294
- X_POLICY_MISSING: 500,
295
- // A declaration fault raised at module evaluation, the same shelf as the line above. The row is
296
- // not a claim it reaches a request — an unmapped code already answers 500 — it is the answer
297
- // being REVIEWED instead of accidental, which is why this table is closed.
298
- X_POLICY_CLAUSE_EMPTY: 500,
299
- X_PERMISSION_UNKNOWN: 500,
300
- // 500, and the page IS the point — deliberately not a 4xx to keep this table quiet.
301
- // `enforce()` was handed a surface no adapter answers to, which reaches a request only through a
302
- // config-driven route table, a surface name off the wire or a JS host; none of those is a value
303
- // the caller can correct, and a 400 would tell them to fix a request that is not the problem.
304
- // It is the third authz-dispatch fault beside the two rows above and takes their status for the
305
- // same reason: the declaration is wrong, not the call.
306
- X_POLICY_SURFACE_UNKNOWN: 500,
307
- // @ultimat3/query — the read declares no id, so no cursor can name a position in it. The one
308
- // paging failure that is NOT the caller's: the fix is an edit to the read's own select, nothing
309
- // the client sends changes the answer, and the report to the on-call monitor is the point.
310
- X_QUERY_NOT_PAGEABLE: 500,
311
- // A read filtering or sorting on a column its loader never selected: the fix is the loader's
312
- // `select`, nothing the caller sends changes it — the same server-side decision as the row above.
313
- X_QUERY_COLUMN_UNSELECTED: 500,
314
- // @ultimat3/i18n — a well-formed tag outside the set this app ships, asserted on a value the
315
- // caller supplied (`assertSupportedLocale`). 400 rather than 406: the http `locale` stage
316
- // negotiates `Accept-Language` and never throws, so the tag that reaches here came from a path,
317
- // query or body the caller wrote — the same place `X_IMAGE_QUERY_INVALID` comes from.
318
- X_LOCALE_UNSUPPORTED: 400,
319
- // @ultimat3/core (declared beside `assertLocale`; `@ultimat3/time` owned it until 16.x) — the
320
- // sibling of the row above, and strictly more the caller's fault: not a tag at all. It was
321
- // pinned as unable to reach a request because the `locale` stage negotiates and never throws,
322
- // which is true of that stage and irrelevant to `?locale=`, a path segment or an action input
323
- // reaching `formatDate` / `formatMoney` / `describeCron`. Those raised it and answered 500,
324
- // paging the on-call for a string the caller typed.
325
- X_LOCALE_INVALID: 400,
326
- // @ultimat3/core — `seal()` / `open()`. All three are reachable from a request (a sealed column
327
- // read inside an action) and none is the caller's doing: the deploy has no key, the ring lost a
328
- // retired key, or a stored value did not authenticate. 500, stated rather than defaulted.
329
- X_SEAL_INVALID: 500,
330
- X_SEAL_KEY_MISSING: 500,
331
- X_SEAL_KEY_UNKNOWN: 500,
332
- // @ultimat3/money — a well-formed code this process carries no row for. The currency table is
333
- // OPEN (`registerCurrency`), and every surface between the wire and the throw accepts any
334
- // `^[A-Z]{3}$`: `@ultimat3/schema`'s `CURRENCY_CODE_PATTERN`, the OpenAPI `pattern` emitted from
335
- // it, and `@ultimat3/entity`'s `char(3)` CHECK. So `{ minor: 100, currency: 'ZWL' }` parses,
336
- // reaches `money()` -> `assertCurrency`, and with no row answered 500 — reporting a value the
337
- // framework's own schema had just accepted to the error monitor. 400 rather than 422, beside
338
- // `X_LOCALE_UNSUPPORTED`: the same shape of mistake, a well-formed value naming something
339
- // outside the set this process carries, and money's `fix:` already instructs the caller.
340
- X_CURRENCY_UNKNOWN: 400,
341
- // @ultimat3/seo — a transform query the caller wrote, so the caller is the one who can fix it.
342
- X_IMAGE_QUERY_INVALID: 400,
343
- // @ultimat3/storage — every one of these is reachable from a route: `/media/*` already serves
344
- // objects, and a mounted `/_storage` serves signed reads and takes signed writes. Without a row
345
- // a missing image answers 500, which reads as an outage instead of a 404.
346
- X_STORAGE_NOT_FOUND: 404,
347
- X_STORAGE_PATH_UNSAFE: 400,
348
- X_STORAGE_TOO_LARGE: 413,
349
- X_STORAGE_TYPE_REJECTED: 415,
350
- X_STORAGE_CHECKSUM_MISMATCH: 422,
351
- X_STORAGE_URL_INVALID: 403,
352
- X_STORAGE_URL_EXPIRED: 410,
353
- // 500, not 403, and the distinction is the whole reason this code exists rather than
354
- // reusing X_STORAGE_URL_INVALID: nothing is wrong with the caller's URL. The disk was
355
- // built with no way to check a signature, which is the operator's misconfiguration and
356
- // not an attacker — reporting it as 403 sends the on-call hunting somebody who is not there.
357
- X_STORAGE_URL_UNVERIFIABLE: 500,
358
- // 409, not the 500 it fell through to: the object exists and the request is well formed — the
359
- // STATE is wrong. A validated upload lands under the quarantine segment and `promoteAttachment`
360
- // refuses it until the app's own scanner calls `releaseQuarantine`, which is a thing the caller
361
- // can do. A 500 would have read as "the server broke" for a workflow working exactly as built.
362
- X_STORAGE_QUARANTINED: 409,
363
- // 409 for the same reason: `promoteAttachment` on a key that is not a pending upload — promoted
364
- // twice, or never uploaded — is a state the caller can see and fix, not a server fault.
365
- X_STORAGE_NOT_PENDING: 409,
366
- // 404, deliberately NOT 403: the org check fires before anything is read, so answering
367
- // "forbidden" would confirm that a key exists to the one caller who must not learn it.
368
- X_STORAGE_ORG_MISMATCH: 404,
369
- // @ultimat3/ui — a form control whose `name` is not a usable field path. The owning slice argued
370
- // for NO ROW, on the grounds that this is a render-time developer error that can never reach
371
- // HTTP, and the argument is right about the code and wrong about the table.
372
- //
373
- // `scripts/error-map-backlog.ts` is the only "no row" this table has, and its own header says
374
- // what an entry there means: "NOT a claim that the code can never cross HTTP … a claim that
375
- // nobody has decided yet", with the ratchet promising only that the undecided set never grows.
376
- // This code HAS been decided, so a pin would record the opposite of what is known and grow the
377
- // one list that may not grow.
378
- //
379
- // So it takes the answer every other decided-and-unreachable code takes — `X_CORS_CONFIG_INVALID`,
380
- // `X_ACTION_JOB_UNBRIDGED`, `X_RATE_LIMIT_NOT_SHARED`. The row is NOT a claim that it reaches a
381
- // request. A code with no row already answers 500 (`DEFAULT_STATUS`); the row changes nothing at
382
- // runtime and makes that answer a reviewed one instead of an accident, which is the whole reason
383
- // this table is closed.
384
- X_UI_FORM_PATH_INVALID: 500,
385
- // @ultimat3/ui — `defineTheme()` refused a brand palette whose RESOLVED channels put a pairing
386
- // below WCAG 2.2 AA. Declaration-time in the ordinary case, and it takes a row rather than a
387
- // backlog entry for the one case that is not: an app that renders a per-tenant brand stylesheet
388
- // during a request throws this ON that request. The author's fault and never the caller's, so
389
- // 500 is the honest class, and a DECLARED 500 rather than an unclassified one — `toProblem`
390
- // blanks the cause of an unclassified failure outside dev, which would hide the one thing that
391
- // makes this error actionable: the role and the measured ratio.
392
- X_UI_CONTRAST_INSUFFICIENT: 500,
393
- // @ultimat3/ui — `<QrCode>` was handed a value past the encoder's version-3 ceiling. The value
394
- // is the app's own (a short URL it minted), never the caller's, so 500 is the honest class —
395
- // and a declared 500 for the contrast code's reason: `toProblem` blanks an unclassified
396
- // failure's cause outside dev, and the cause is where the byte count and the ceiling are.
397
- X_UI_QR_CAPACITY: 500,
398
- // @ultimat3/render — an island handed props it cannot carry: an undeclared key, a value that is
399
- // not JSON, or a bag over `ISLAND_PROPS_MAX_BYTES`. The author's fault and never the caller's,
400
- // so 500 is the honest class — and it HAS to be a declared 500. Without a row the code was an
401
- // unclassified failure, and `toProblem` blanks the cause of one of those outside dev
402
- // (`isUnclassifiedFailure`): a 34-row catalog over the cap took a page down with a problem
403
- // document that said "the details are in this process's logs" about an error whose whole
404
- // value is the sentence naming the prop and its bytes. Measured on ai-maxxing, 2026-09-05.
405
- X_ISLAND_PROPS_INVALID: 500,
406
- // A loader answered `withStatus` with a 3xx, or a status outside 200–599. Raised INSIDE the
407
- // request that ran the loader, so it needs a row for the same reason the line above does: an
408
- // unclassified 500 blanks the sentence naming the status and the `redirect()` to use instead.
409
- X_ROUTE_STATUS_INVALID: 500,
410
- // `asset('assets/…')` named a file that is not on disk. The static build refuses it, but an `ssr`
411
- // page calls `asset()` INSIDE the request it renders, so it reaches a caller there — a deploy
412
- // defect, never the visitor's, hence 500, declared so the cause naming the missing file survives.
413
- X_ASSET_MISSING: 500,
414
- // @ultimat3/seo — a `meta.links` entry an `ssr` page's `meta()` built from data: an unsafe href,
415
- // a preload with no `as`. Thrown while that request renders its head, so it reaches the caller —
416
- // an authoring defect, never the visitor's, hence 500 with the cause naming the link intact.
417
- X_SEO_LINK_INVALID: 500,
418
- // @ultimat3/mail
419
- // The deployment configured no transport. It reaches a caller only through an inline
420
- // `send(…, { sync: true })` inside a request; the queued path dead-letters instead. A server-side
421
- // configuration fault either way, so 500 and never a 4xx — nothing the caller sent is wrong, and
422
- // this is exactly the condition somebody should be paged for.
423
- X_MAIL_CREDENTIAL_MISSING: 500,
424
- // @ultimat3/mcp — the one MCP code that is answered on a REQUEST rather than inside a JSON-RPC
425
- // envelope, which is what the rest of that package's backlog group says about the others: the
426
- // transport refused before dispatch, so there is no call to answer. 429 because
427
- // `mcpHttpRoute` already builds that response by hand (`transport-http.ts`'s `throttled`), with
428
- // `retry-after` beside it. The row is what keeps the two surfaces from disagreeing the day the
429
- // MCP host is mounted inside this pipeline — a code that renders 429 on one and 500 on the other
430
- // is exactly the split this table exists to prevent.
431
- X_MCP_RATE_LIMITED: 429,
432
- // The second MCP code answered on a request before dispatch, and 413 for the same reason the row
433
- // above is 429: `transport-http.ts` already answers it with that status by hand.
434
- X_MCP_BODY_TOO_LARGE: 413,
435
- // @ultimat3/core
436
- // The caller asked for a format the pipeline cannot produce (`?f=avif`): the request names an
437
- // unsupported representation, which is 415 — not a 500, which would blame the server for it.
438
- X_IMAGE_UNSUPPORTED: 415,
439
- // A page token this server minted and the caller echoed back, and it did not verify — tampered,
440
- // or replayed against another read. The caller's value and the caller's repair ("request the
441
- // first page again"), so it belongs beside `X_IMAGE_QUERY_INVALID` at 400 and not at 500.
442
- X_CURSOR_INVALID: 400,
443
- // Raised by `markReady()` while a role STARTS, in a process whose lifecycle already drained — so
444
- // no request is ever answered with it, and the row exists for the reason the construction-time
445
- // rows above do: this table is the closed one, and a code with no row is a 500 anyway.
446
- X_LIFECYCLE_DRAINED: 500,
447
- X_NOT_IMPLEMENTED: 501,
448
- X_TIMEOUT: 504,
449
- X_ABORTED: 499,
450
- // The twin of `X_ABORTED`, and it answers the same because the outcome is the same: the caller
451
- // went away there, the caller's generation moved on here, and in both cases nobody will act on
452
- // the answer. Deliberately not 409 — that spelling asks the client to reconcile and try again,
453
- // and a fenced answer has nothing to reconcile against.
454
- X_SUPERSEDED: 499,
455
- // Plan 101's client seam. A typed call made from a server handler: an upstream that gave no usable
456
- // answer is 502, the fence is `X_SUPERSEDED`'s twin. The rest are browser-side or declaration-time
457
- // refusals no request carries; their rows keep the table closed.
458
- X_CLIENT_TRANSPORT_FAILED: 502,
459
- X_CLIENT_RECORD_ENVELOPE_INVALID: 502,
460
- X_CLIENT_SCOPE_CHANGED: 499,
461
- X_CHANNEL_DECLARATION_INVALID: 500,
462
- X_LOCAL_STORE_UNAVAILABLE: 500,
463
- X_MUTATOR_CLOCK_MISSING: 500,
464
- X_REALTIME_UNINSTALLED: 500,
465
- // Boot-time: a realtime config and its environment that disagree refuse before any request.
466
- X_REALTIME_TOPOLOGY: 500,
467
- // The replicator's own Postgres connection failed TLS. A role that never answers a request, so the
468
- // row keeps the table closed at 500, beside `X_REALTIME_TOPOLOGY`.
469
- X_REPLICATION_TLS: 500,
470
- // A browser page on a foreign origin asked for a socket — refused, not unauthenticated: 403.
471
- X_SOCKET_ORIGIN_REFUSED: 403,
472
- X_RECORD_KEY_MISSING: 500,
473
- X_RECORD_REJECTED: 500,
474
- X_SYNC_UNCONFIGURED: 500,
475
- X_INTERNAL: 500,
476
- // @ultimat3/jobs — the job is running and cannot be removed
477
- X_JOB_NOT_REMOVABLE: 409,
478
- // @ultimat3/jobs — the job is not waiting on its run time
479
- X_JOB_NOT_PROMOTABLE: 409,
480
- // @ultimat3/jobs — a job list page was asked for outside its bounds
481
- X_JOB_PAGE_INVALID: 400,
29
+ ...HTTP_ERROR_STATUS,
30
+ ...TIER_0_ERROR_STATUS,
31
+ ...TIER_1_ERROR_STATUS,
32
+ ...TIER_2_ERROR_STATUS,
33
+ ...TIER_3_ERROR_STATUS,
34
+ ...TIER_4_ERROR_STATUS,
482
35
  // The keys are LITERAL — deliberately not `Readonly<Record<string, number>>`, which is what the
483
36
  // annotation used to say. This table is the closed one, so `ERROR_STATUS.X_QUERY_NOT_PAGABLE`
484
37
  // has to be a compile error rather than an `undefined` a test then asserts `toBeNumber()` on.