@pithy-sh/core 0.1.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.
- package/LICENSE +21 -0
- package/package.json +47 -0
- package/pithy.manifest.json +74 -0
- package/src/address/address.ts +83 -0
- package/src/audit/auditEvent.ts +130 -0
- package/src/audit/recorder.ts +22 -0
- package/src/capability/bindings.ts +196 -0
- package/src/capability/capability.ts +555 -0
- package/src/capability/client.ts +136 -0
- package/src/capability/compose.ts +76 -0
- package/src/capability/composition.ts +98 -0
- package/src/capability/config.ts +19 -0
- package/src/capability/devSecret.ts +42 -0
- package/src/capability/manifest.ts +580 -0
- package/src/capability/secretOrigin.ts +253 -0
- package/src/capability/settings.ts +155 -0
- package/src/capability/validateBindings.ts +43 -0
- package/src/capability/vanishingKey.ts +92 -0
- package/src/cloudflare-test.d.ts +20 -0
- package/src/controlPlane/audit/actions.ts +81 -0
- package/src/controlPlane/capability.ts +228 -0
- package/src/controlPlane/config/config.ts +195 -0
- package/src/controlPlane/context.ts +63 -0
- package/src/controlPlane/data/connection.ts +123 -0
- package/src/controlPlane/data/keyLifecycle.ts +159 -0
- package/src/controlPlane/data/replay.ts +39 -0
- package/src/controlPlane/data/tables.ts +51 -0
- package/src/controlPlane/discovery/adminRoute.ts +250 -0
- package/src/controlPlane/discovery/configuration.ts +280 -0
- package/src/controlPlane/discovery/drift.ts +100 -0
- package/src/controlPlane/discovery/health.ts +213 -0
- package/src/controlPlane/discovery/healthSummary.ts +486 -0
- package/src/controlPlane/error/errors.ts +125 -0
- package/src/controlPlane/http/cors.ts +244 -0
- package/src/controlPlane/http/guard.ts +223 -0
- package/src/controlPlane/http/handlers.ts +346 -0
- package/src/controlPlane/http/responses.ts +92 -0
- package/src/controlPlane/http/routes.ts +115 -0
- package/src/controlPlane/http/schemas.ts +70 -0
- package/src/controlPlane/http/verify.ts +198 -0
- package/src/controlPlane/migrations/0001_init.ts +105 -0
- package/src/controlPlane/replay/d1Guard.ts +87 -0
- package/src/controlPlane/replay/guard.ts +55 -0
- package/src/controlPlane/replay/kvGuard.ts +143 -0
- package/src/controlPlane/scope/scope.ts +102 -0
- package/src/controlPlane/token/base64url.ts +65 -0
- package/src/controlPlane/token/claims.ts +151 -0
- package/src/controlPlane/token/digest.ts +63 -0
- package/src/controlPlane/token/jws.ts +112 -0
- package/src/controlPlane/token/mint.ts +93 -0
- package/src/controlPlane/wire.ts +138 -0
- package/src/createBackend.ts +292 -0
- package/src/createEntrypoint.ts +125 -0
- package/src/data/boundParameters.ts +197 -0
- package/src/data/codecs.ts +160 -0
- package/src/data/cursor.ts +127 -0
- package/src/data/databases.ts +84 -0
- package/src/data/db.ts +53 -0
- package/src/data/withD1Retry.ts +176 -0
- package/src/entitlement/entitlement.ts +191 -0
- package/src/entitlement/gateScan.ts +107 -0
- package/src/entitlement/require.ts +199 -0
- package/src/env/ambient.ts +67 -0
- package/src/env/ci.ts +43 -0
- package/src/env/stem.ts +34 -0
- package/src/error/cause.ts +208 -0
- package/src/error/client.ts +43 -0
- package/src/error/extend.ts +135 -0
- package/src/error/http.ts +92 -0
- package/src/error/payload.ts +2195 -0
- package/src/error/pithyError.ts +281 -0
- package/src/error/terminal.ts +36 -0
- package/src/http/authContext.ts +29 -0
- package/src/http/routeContract.ts +115 -0
- package/src/http/sameOrigin.ts +67 -0
- package/src/http/signedWebhook.ts +415 -0
- package/src/http/validation.ts +41 -0
- package/src/http/verification.ts +25 -0
- package/src/i18n/acceptLanguage.ts +70 -0
- package/src/i18n/catalog.ts +113 -0
- package/src/i18n/locale.ts +153 -0
- package/src/i18n/localeMarker.ts +116 -0
- package/src/i18n/match.ts +111 -0
- package/src/i18n/registry.ts +78 -0
- package/src/i18n/translator.ts +168 -0
- package/src/index.ts +116 -0
- package/src/kv/kv.ts +437 -0
- package/src/kv/namespaces.ts +102 -0
- package/src/logger/local.ts +91 -0
- package/src/logger/logger.ts +145 -0
- package/src/logger/record.ts +83 -0
- package/src/logger/worker.ts +117 -0
- package/src/migrations/batch.ts +226 -0
- package/src/migrations/bookkeeping.ts +85 -0
- package/src/migrations/owner.ts +166 -0
- package/src/migrations/registry.ts +121 -0
- package/src/migrations/runner.ts +295 -0
- package/src/naming/domains.ts +194 -0
- package/src/naming/environment.ts +224 -0
- package/src/naming/feature.ts +162 -0
- package/src/naming/limits.ts +223 -0
- package/src/naming/provisionScope.ts +143 -0
- package/src/naming/resource.ts +266 -0
- package/src/naming/resourceNames.ts +174 -0
- package/src/naming/segment.ts +32 -0
- package/src/projection/asRead.ts +211 -0
- package/src/projection/published.ts +210 -0
- package/src/schema/describedness.ts +250 -0
- package/src/seed/compose.ts +94 -0
- package/src/seed/devLogin.ts +67 -0
- package/src/seed/exampleIdentities.ts +43 -0
- package/src/seed/metadata.ts +27 -0
- package/src/seed/seed.ts +306 -0
- package/src/seed/seededRows.ts +41 -0
- package/src/seed/writeD1.ts +103 -0
- package/src/seed/writeKv.ts +99 -0
- package/src/semver/semver.ts +156 -0
- package/src/text/comments.ts +165 -0
- package/src/version.generated.ts +16 -0
- package/src/worker/health.ts +42 -0
- package/src/worker/identity.ts +243 -0
- package/src/workflow/bindings.ts +58 -0
- package/src/workflow/dispatch.ts +240 -0
- package/src/workflow/dispatchRoute.ts +184 -0
- package/src/workflow/faults.ts +219 -0
- package/src/workflow/host.ts +307 -0
- package/src/workflow/hostEntry.ts +71 -0
- package/src/workflow/hostEnv.ts +258 -0
- package/src/workflow/loopback.ts +149 -0
- package/src/workflow/naming.ts +170 -0
- package/src/workflow/register.ts +44 -0
- package/src/workflow/schemas.ts +84 -0
- package/src/workflow/spec.ts +86 -0
- package/src/workflow/stepMessage.ts +160 -0
|
@@ -0,0 +1,2195 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { MessageParams } from "../i18n/catalog";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The error object model. One Zod schema is the whole definition of every failure Pithy
|
|
9
|
+
* can emit: the discriminated union `KitErrorPayload` is the kit's closed taxonomy, keyed on a
|
|
10
|
+
* machine-readable `code`, and a new capability's codes go there. `ErrorPayload` is that plus
|
|
11
|
+
* one open member for an adopter's own domains — see the seam at the foot of this file, and
|
|
12
|
+
* never switch exhaustively over it. The `PithyError` class (./pithyError) is only the
|
|
13
|
+
* throw/catch vehicle that carries one of these payloads; the surfaces (HTTP, terminal) are
|
|
14
|
+
* encoders over this schema. `.describe()` on every field feeds the generated error catalog.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** A single field-level validation failure, mirrored from a Zod issue for the wire. */
|
|
18
|
+
export const ValidationIssue = z
|
|
19
|
+
.object({
|
|
20
|
+
path: z
|
|
21
|
+
.array(z.union([z.string(), z.number()]).describe("One step of the path: an object key, or an array index."))
|
|
22
|
+
.describe("Path to the offending field, e.g. ['user', 0, 'email']."),
|
|
23
|
+
message: z.string().describe("Human-readable reason this field failed."),
|
|
24
|
+
code: z.string().describe("Zod issue code, e.g. 'invalid_type', 'too_small'."),
|
|
25
|
+
})
|
|
26
|
+
.describe("A single field-level validation failure, mirrored from a ZodIssue.");
|
|
27
|
+
export type ValidationIssue = z.infer<typeof ValidationIssue>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Fields safe to expose to clients. Present on every member, public and wire alike.
|
|
31
|
+
*
|
|
32
|
+
* **`params` is here because the server never localizes an error.** `message` is English permanently:
|
|
33
|
+
* it is the operator's diagnostic in the log line and the audit row *and* the fallback for every client
|
|
34
|
+
* that does not translate, and translating it server-side trades the first for the second against a
|
|
35
|
+
* locale the caller may never have declared. So the wire carries the two halves a translation needs and
|
|
36
|
+
* nothing more — the `code`, which is already the discriminator and *is* the catalog key
|
|
37
|
+
* (`../i18n/catalog`), and the values its placeholders take. A translating client renders
|
|
38
|
+
* `t.maybe(payload.code, payload.params) ?? payload.message`.
|
|
39
|
+
*
|
|
40
|
+
* Optional and absent rather than `.default({})`, so an error that names no placeholder puts no key on
|
|
41
|
+
* the wire and every client already deployed parses exactly the bytes it parsed yesterday.
|
|
42
|
+
*/
|
|
43
|
+
const publicFields = {
|
|
44
|
+
message: z.string().describe("Public, safe-to-expose summary. Sent to clients and shown in the terminal."),
|
|
45
|
+
params: MessageParams.optional().describe(
|
|
46
|
+
"Values a translating client interpolates into its own wording for this code. Client-facing, so it crosses the boundary with `message` — unlike `action` and `detail`, which the HTTP codec strips.",
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The operator-only fields. On `ErrorPayload`, never on the public/wire shape.
|
|
52
|
+
*
|
|
53
|
+
* **`action` is a remedy for somebody with the project checked out.** Read the ones the kit ships and
|
|
54
|
+
* that is unmistakable: they name `pithy` commands, files in the adopter's repository, wrangler
|
|
55
|
+
* bindings, D1 databases, provider consoles. Sent to a browser, that is a description of the
|
|
56
|
+
* deployment — so it travels with `detail`, on the surfaces an operator reads: the terminal, the CLI's
|
|
57
|
+
* `--json` line, a log, an audit row. A remedy the *caller* needs is not a third audience; it is
|
|
58
|
+
* `message`, the field whose whole definition is "safe to expose".
|
|
59
|
+
*
|
|
60
|
+
* Grouped with `detail` rather than checked at each throw site on purpose. A throw site is where this
|
|
61
|
+
* codebase has repeatedly fixed one instance and left its siblings; there is nothing here for a throw
|
|
62
|
+
* site to get right, because the wire shape has no such key to fill.
|
|
63
|
+
*/
|
|
64
|
+
const internalFields = {
|
|
65
|
+
action: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe(
|
|
69
|
+
"Operator remediation hint. Becomes the CLI action line. NEVER serialized to clients — the HTTP codec strips it.",
|
|
70
|
+
),
|
|
71
|
+
detail: z
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Internal context for logs + audit. NEVER serialized to clients — the HTTP codec strips it."),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// One public member per code. The `code` literal is the discriminator; `status` is pinned
|
|
78
|
+
// to the one HTTP status that code maps to, so a mismatched pair fails validation.
|
|
79
|
+
const InvalidInputPublic = z
|
|
80
|
+
.object({
|
|
81
|
+
code: z.literal("validation/invalid_input").describe("Input failed validation at a boundary."),
|
|
82
|
+
status: z.literal(400).describe("Bad Request."),
|
|
83
|
+
issues: z.array(ValidationIssue).describe("Field-level validation failures."),
|
|
84
|
+
...publicFields,
|
|
85
|
+
})
|
|
86
|
+
.describe("Input failed validation at a boundary (400).");
|
|
87
|
+
|
|
88
|
+
const InvalidTokenPublic = z
|
|
89
|
+
.object({
|
|
90
|
+
code: z.literal("auth/invalid_token").describe("The bearer/session credential is missing, expired, or invalid."),
|
|
91
|
+
status: z.literal(401).describe("Unauthorized."),
|
|
92
|
+
...publicFields,
|
|
93
|
+
})
|
|
94
|
+
.describe("The caller's credential is missing, expired, or invalid (401).");
|
|
95
|
+
|
|
96
|
+
const ForbiddenPublic = z
|
|
97
|
+
.object({
|
|
98
|
+
code: z.literal("auth/forbidden").describe("The caller is authenticated but not allowed to do this."),
|
|
99
|
+
status: z.literal(403).describe("Forbidden."),
|
|
100
|
+
...publicFields,
|
|
101
|
+
})
|
|
102
|
+
.describe("The caller is authenticated but lacks permission (403).");
|
|
103
|
+
|
|
104
|
+
const AuthProviderUnavailablePublic = z
|
|
105
|
+
.object({
|
|
106
|
+
code: z
|
|
107
|
+
.literal("auth/provider_unavailable")
|
|
108
|
+
.describe(
|
|
109
|
+
"A social sign-in provider this deployment enables could not be served, because its credential would not resolve for this environment. The other sign-in methods are unaffected — that is the whole reason this is not a 500. It is a separate code from `core/not_found` on purpose: Better Auth answers a provider nobody configured with a 404, and a caller who signs in with that provider every day must be able to tell 'nobody enabled this' from 'this is broken right now'.",
|
|
110
|
+
),
|
|
111
|
+
status: z
|
|
112
|
+
.literal(503)
|
|
113
|
+
.describe(
|
|
114
|
+
"Service Unavailable — one of this service's own dependencies, the provider's credential, could not be read. Not 502/504: nothing was asked of the provider, so no hop failed or timed out.",
|
|
115
|
+
),
|
|
116
|
+
...publicFields,
|
|
117
|
+
})
|
|
118
|
+
.describe("An enabled social sign-in provider could not be served (503).");
|
|
119
|
+
|
|
120
|
+
const NotFoundPublic = z
|
|
121
|
+
.object({
|
|
122
|
+
code: z.literal("core/not_found").describe("The requested resource does not exist."),
|
|
123
|
+
status: z.literal(404).describe("Not Found."),
|
|
124
|
+
...publicFields,
|
|
125
|
+
})
|
|
126
|
+
.describe("The requested resource does not exist (404).");
|
|
127
|
+
|
|
128
|
+
const ConflictPublic = z
|
|
129
|
+
.object({
|
|
130
|
+
code: z.literal("core/conflict").describe("The request conflicts with current state (e.g. a duplicate)."),
|
|
131
|
+
status: z.literal(409).describe("Conflict."),
|
|
132
|
+
...publicFields,
|
|
133
|
+
})
|
|
134
|
+
.describe("The request conflicts with current state (409).");
|
|
135
|
+
|
|
136
|
+
const RateLimitPublic = z
|
|
137
|
+
.object({
|
|
138
|
+
code: z.literal("rate_limit/exceeded").describe("The caller has sent too many requests."),
|
|
139
|
+
status: z.literal(429).describe("Too Many Requests."),
|
|
140
|
+
...publicFields,
|
|
141
|
+
})
|
|
142
|
+
.describe("The caller has exceeded a rate limit (429).");
|
|
143
|
+
|
|
144
|
+
const InternalPublic = z
|
|
145
|
+
.object({
|
|
146
|
+
code: z.literal("core/internal").describe("An unexpected server-side failure."),
|
|
147
|
+
status: z.literal(500).describe("Internal Server Error."),
|
|
148
|
+
...publicFields,
|
|
149
|
+
})
|
|
150
|
+
.describe("An unexpected server-side failure (500).");
|
|
151
|
+
|
|
152
|
+
const UpstreamFailedPublic = z
|
|
153
|
+
.object({
|
|
154
|
+
code: z
|
|
155
|
+
.literal("core/upstream_failed")
|
|
156
|
+
.describe(
|
|
157
|
+
"A service this one depends on and does not control failed: the connection was refused, the answer carried an error status, or the body could not be read as what it claimed to be. Deliberately distinct from `core/internal` — a 500 says this service broke, and reporting someone else's outage that way sends the operator to read the wrong logs.",
|
|
158
|
+
),
|
|
159
|
+
status: z.literal(502).describe("Bad Gateway — the fault is upstream of this service."),
|
|
160
|
+
...publicFields,
|
|
161
|
+
})
|
|
162
|
+
.describe("A dependency this service does not control failed (502).");
|
|
163
|
+
|
|
164
|
+
const UpstreamTimeoutPublic = z
|
|
165
|
+
.object({
|
|
166
|
+
code: z
|
|
167
|
+
.literal("core/upstream_timeout")
|
|
168
|
+
.describe(
|
|
169
|
+
"A service this one depends on did not answer inside its deadline. Separate from `core/upstream_failed` because the two say different things to a caller: a failed call is answered, a timed-out one may still be applied upstream, so only this one makes a blind retry a question worth asking.",
|
|
170
|
+
),
|
|
171
|
+
status: z
|
|
172
|
+
.literal(504)
|
|
173
|
+
.describe(
|
|
174
|
+
"Gateway Timeout — the hop ran out of time. Not 503: `Service Unavailable` claims *this* service is the one that cannot serve, which sends a caller (and every retry heuristic that reads it) at the wrong system. 503 stays for a dependency the capability cannot work without at all — see `payments/provider_unavailable`.",
|
|
175
|
+
),
|
|
176
|
+
...publicFields,
|
|
177
|
+
})
|
|
178
|
+
.describe("A dependency this service does not control did not answer in time (504).");
|
|
179
|
+
|
|
180
|
+
// --- @pithy-sh/core: durable-job (Cloudflare Workflow) dispatch codes ---
|
|
181
|
+
|
|
182
|
+
const InvalidWorkflowParamsPublic = z
|
|
183
|
+
.object({
|
|
184
|
+
code: z
|
|
185
|
+
.literal("core/invalid_workflow_params")
|
|
186
|
+
.describe(
|
|
187
|
+
"The parameters passed to a workflow failed its declared schema. Raised at the call site, before the binding is touched, so a malformed trigger never becomes a failed step inside a running instance.",
|
|
188
|
+
),
|
|
189
|
+
status: z.literal(400).describe("Bad Request."),
|
|
190
|
+
...publicFields,
|
|
191
|
+
})
|
|
192
|
+
.describe("Workflow dispatch parameters failed validation (400).");
|
|
193
|
+
|
|
194
|
+
const MissingWorkflowBindingPublic = z
|
|
195
|
+
.object({
|
|
196
|
+
code: z
|
|
197
|
+
.literal("core/missing_workflow_binding")
|
|
198
|
+
.describe(
|
|
199
|
+
"A registered workflow's binding is absent from the Worker env — the host worker is not deployed or not bound.",
|
|
200
|
+
),
|
|
201
|
+
status: z.literal(500).describe("Internal Server Error."),
|
|
202
|
+
...publicFields,
|
|
203
|
+
})
|
|
204
|
+
.describe("A declared workflow binding is missing from the Worker env (500).");
|
|
205
|
+
|
|
206
|
+
const UnknownWorkflowPublic = z
|
|
207
|
+
.object({
|
|
208
|
+
code: z
|
|
209
|
+
.literal("core/unknown_workflow")
|
|
210
|
+
.describe("Dispatch named a `<capability>/<job>` key that no composed capability registers."),
|
|
211
|
+
status: z.literal(500).describe("Internal Server Error."),
|
|
212
|
+
...publicFields,
|
|
213
|
+
})
|
|
214
|
+
.describe("No registered workflow answers to the dispatch key (500).");
|
|
215
|
+
|
|
216
|
+
const WorkflowFailedPublic = z
|
|
217
|
+
.object({
|
|
218
|
+
code: z
|
|
219
|
+
.literal("core/workflow_failed")
|
|
220
|
+
.describe(
|
|
221
|
+
"A dispatched Workflow instance reached a terminal state without completing, and the fault that ended it could not be attributed to a code of its own. **Terminal, and that is the whole reason it is not `cloudflare/request_failed`**: the dispatch was delivered, the instance ran, and the run is over — re-driving the same request reaches the same end. A step that raised a code the kit pins a status for does not arrive here at all; it arrives under that code (pithy-sh/pithy#365).",
|
|
222
|
+
),
|
|
223
|
+
status: z
|
|
224
|
+
.literal(500)
|
|
225
|
+
.describe(
|
|
226
|
+
"Internal Server Error — the durable job is the kit's own code in the operator's Worker, and its step journal is where the answer is. Deliberately not 502: that says the hop to Cloudflare failed and invites a retry of a request that was delivered and answered.",
|
|
227
|
+
),
|
|
228
|
+
...publicFields,
|
|
229
|
+
})
|
|
230
|
+
.describe("A dispatched Workflow ran and ended without completing, with no attributable code (500).");
|
|
231
|
+
|
|
232
|
+
// --- @pithy-sh/core: the `signed-webhook` verification strategy ---
|
|
233
|
+
|
|
234
|
+
const WebhookUnverifiedPublic = z
|
|
235
|
+
.object({
|
|
236
|
+
code: z
|
|
237
|
+
.literal("core/webhook_unverified")
|
|
238
|
+
.describe(
|
|
239
|
+
"An inbound webhook did not prove it came from the sender that holds the signing secret. Deliberately one code for every failing step — no header, an unreadable one, a delivery outside the freshness window, or an HMAC that does not match these bytes — so a forger cannot use the response to learn which check it tripped. `detail` names the step for the log, and the HTTP codec strips it.",
|
|
240
|
+
),
|
|
241
|
+
status: z.literal(401).describe("Unauthorized — the delivery did not prove its origin."),
|
|
242
|
+
...publicFields,
|
|
243
|
+
})
|
|
244
|
+
.describe("An inbound webhook failed its signature check (401).");
|
|
245
|
+
|
|
246
|
+
// --- @pithy-sh/cloudflare: out-of-Worker CF REST client codes ---
|
|
247
|
+
|
|
248
|
+
const CloudflareNotConfiguredPublic = z
|
|
249
|
+
.object({
|
|
250
|
+
code: z
|
|
251
|
+
.literal("cloudflare/not_configured")
|
|
252
|
+
.describe("The CF REST client is missing required configuration (token, account, or a resource id)."),
|
|
253
|
+
status: z.literal(500).describe("Internal Server Error — a configuration fault, not a client request."),
|
|
254
|
+
...publicFields,
|
|
255
|
+
})
|
|
256
|
+
.describe("The Cloudflare REST client is not fully configured (500).");
|
|
257
|
+
|
|
258
|
+
const CloudflareRequestFailedPublic = z
|
|
259
|
+
.object({
|
|
260
|
+
code: z.literal("cloudflare/request_failed").describe("A call to the Cloudflare REST API failed."),
|
|
261
|
+
status: z.literal(502).describe("Bad Gateway — the upstream Cloudflare API returned an error."),
|
|
262
|
+
...publicFields,
|
|
263
|
+
})
|
|
264
|
+
.describe("A Cloudflare REST API call failed (502).");
|
|
265
|
+
|
|
266
|
+
const CloudflareInvalidResponsePublic = z
|
|
267
|
+
.object({
|
|
268
|
+
code: z
|
|
269
|
+
.literal("cloudflare/invalid_response")
|
|
270
|
+
.describe("A Cloudflare REST API response failed validation against its expected shape."),
|
|
271
|
+
status: z.literal(502).describe("Bad Gateway — the upstream Cloudflare API returned an unexpected shape."),
|
|
272
|
+
...publicFields,
|
|
273
|
+
})
|
|
274
|
+
.describe("A Cloudflare REST API response did not match its expected shape (502).");
|
|
275
|
+
|
|
276
|
+
// --- @pithy-sh/secrets: encrypted secret store + CLI codes ---
|
|
277
|
+
|
|
278
|
+
const SecretNotFoundPublic = z
|
|
279
|
+
.object({
|
|
280
|
+
code: z.literal("secrets/not_found").describe("A requested secret is not present in the store."),
|
|
281
|
+
status: z.literal(404).describe("Not Found."),
|
|
282
|
+
...publicFields,
|
|
283
|
+
})
|
|
284
|
+
.describe("A requested secret does not exist in the store (404).");
|
|
285
|
+
|
|
286
|
+
const SecretAlreadyExistsPublic = z
|
|
287
|
+
.object({
|
|
288
|
+
code: z
|
|
289
|
+
.literal("secrets/already_exists")
|
|
290
|
+
.describe("A secret with this name already exists; `create` refuses to overwrite it."),
|
|
291
|
+
status: z.literal(409).describe("Conflict."),
|
|
292
|
+
...publicFields,
|
|
293
|
+
})
|
|
294
|
+
.describe("A secret with this name already exists (409).");
|
|
295
|
+
|
|
296
|
+
const SecretInvalidValuePublic = z
|
|
297
|
+
.object({
|
|
298
|
+
code: z.literal("secrets/invalid_value").describe("A secret value failed validation against its registry schema."),
|
|
299
|
+
status: z.literal(400).describe("Bad Request."),
|
|
300
|
+
...publicFields,
|
|
301
|
+
})
|
|
302
|
+
.describe("A secret value failed validation against its registry schema (400).");
|
|
303
|
+
|
|
304
|
+
const SecretCryptoFailedPublic = z
|
|
305
|
+
.object({
|
|
306
|
+
code: z
|
|
307
|
+
.literal("secrets/crypto_failed")
|
|
308
|
+
.describe("Encrypting or decrypting a secret failed (a missing key version, or unreadable ciphertext)."),
|
|
309
|
+
status: z.literal(500).describe("Internal Server Error — a crypto or key-configuration fault."),
|
|
310
|
+
...publicFields,
|
|
311
|
+
})
|
|
312
|
+
.describe("Encrypting or decrypting a secret failed (500).");
|
|
313
|
+
|
|
314
|
+
const SecretRotationUnrecordedPublic = z
|
|
315
|
+
.object({
|
|
316
|
+
code: z
|
|
317
|
+
.literal("secrets/rotation_unrecorded")
|
|
318
|
+
.describe(
|
|
319
|
+
"A secret's issuer rolled the credential and the store did not take its successor. The previous value is dead where it was issued, the new one was never recorded, and no retry repairs it — rolling again produces a third value and loses the second. Its own code because the operator's next move is unlike every other secrets failure: a human, in the issuer's console, now.",
|
|
320
|
+
),
|
|
321
|
+
status: z
|
|
322
|
+
.literal(500)
|
|
323
|
+
.describe("Internal Server Error — this side failed to record what a third party successfully did."),
|
|
324
|
+
...publicFields,
|
|
325
|
+
})
|
|
326
|
+
.describe("A credential was rolled at its issuer and its successor was not stored (500).");
|
|
327
|
+
|
|
328
|
+
const SecretRotationUnsupportedPublic = z
|
|
329
|
+
.object({
|
|
330
|
+
code: z
|
|
331
|
+
.literal("secrets/rotation_unsupported")
|
|
332
|
+
.describe(
|
|
333
|
+
"This secret cannot be rotated through the surface that was asked. Not a refusal of the caller and not a fault: a Worker holds one environment's store and its own master key, so a secret whose value lives in Cloudflare's account-level Secrets Store, or whose value must be identical across every environment, is outside what any single Worker can replace. The `pithy secrets rotate` command holds the whole project and can. Its own code so a client can tell 'ask somewhere else' from 'you may not' and from 'it broke'.",
|
|
334
|
+
),
|
|
335
|
+
status: z
|
|
336
|
+
.literal(409)
|
|
337
|
+
.describe(
|
|
338
|
+
"Conflict — the request is well-formed and authorized, and this secret's own declaration is what puts it out of reach here.",
|
|
339
|
+
),
|
|
340
|
+
...publicFields,
|
|
341
|
+
})
|
|
342
|
+
.describe("This secret cannot be rotated through the surface that was asked (409).");
|
|
343
|
+
|
|
344
|
+
// --- @pithy-sh/email: job-table email platform codes ---
|
|
345
|
+
|
|
346
|
+
const EmailTemplateNotFoundPublic = z
|
|
347
|
+
.object({
|
|
348
|
+
code: z.literal("email/template_not_found").describe("The requested email template id is not registered."),
|
|
349
|
+
status: z.literal(404).describe("Not Found."),
|
|
350
|
+
...publicFields,
|
|
351
|
+
})
|
|
352
|
+
.describe("The requested email template does not exist (404).");
|
|
353
|
+
|
|
354
|
+
const EmailInvalidPayloadPublic = z
|
|
355
|
+
.object({
|
|
356
|
+
code: z
|
|
357
|
+
.literal("email/invalid_payload")
|
|
358
|
+
.describe("A template's input variables failed validation against its payload schema."),
|
|
359
|
+
status: z.literal(400).describe("Bad Request."),
|
|
360
|
+
...publicFields,
|
|
361
|
+
})
|
|
362
|
+
.describe("Email template payload failed validation against its schema (400).");
|
|
363
|
+
|
|
364
|
+
const EmailInvalidTokenPublic = z
|
|
365
|
+
.object({
|
|
366
|
+
code: z
|
|
367
|
+
.literal("email/invalid_token")
|
|
368
|
+
.describe(
|
|
369
|
+
"A callback token (click/open/unsubscribe) is malformed, expired, forged, or signed by an unknown key.",
|
|
370
|
+
),
|
|
371
|
+
status: z.literal(400).describe("Bad Request — the signed token did not verify."),
|
|
372
|
+
...publicFields,
|
|
373
|
+
})
|
|
374
|
+
.describe("An email callback token failed verification (400).");
|
|
375
|
+
|
|
376
|
+
const EmailSuppressedPublic = z
|
|
377
|
+
.object({
|
|
378
|
+
code: z
|
|
379
|
+
.literal("email/suppressed")
|
|
380
|
+
.describe("The recipient address is on the suppression list and cannot be sent to."),
|
|
381
|
+
status: z.literal(409).describe("Conflict — the address is suppressed."),
|
|
382
|
+
...publicFields,
|
|
383
|
+
})
|
|
384
|
+
.describe("The recipient is suppressed and was not sent to (409).");
|
|
385
|
+
|
|
386
|
+
const EmailRateLimitedPublic = z
|
|
387
|
+
.object({
|
|
388
|
+
code: z
|
|
389
|
+
.literal("email/rate_limited")
|
|
390
|
+
.describe("The Email Service rejected the send for a rate or daily-quota limit; the send is retryable."),
|
|
391
|
+
status: z.literal(429).describe("Too Many Requests."),
|
|
392
|
+
...publicFields,
|
|
393
|
+
})
|
|
394
|
+
.describe("The Email Service rate or daily limit was hit (429).");
|
|
395
|
+
|
|
396
|
+
const EmailSendFailedPublic = z
|
|
397
|
+
.object({
|
|
398
|
+
code: z
|
|
399
|
+
.literal("email/send_failed")
|
|
400
|
+
.describe(
|
|
401
|
+
"A send through the Email Service binding failed for a non-retryable reason (validation, sender, content).",
|
|
402
|
+
),
|
|
403
|
+
status: z.literal(502).describe("Bad Gateway — the upstream Email Service returned a terminal error."),
|
|
404
|
+
...publicFields,
|
|
405
|
+
})
|
|
406
|
+
.describe("A send through the Email Service failed terminally (502).");
|
|
407
|
+
|
|
408
|
+
// --- @pithy-sh/turnstile: humanity-check middleware codes ---
|
|
409
|
+
|
|
410
|
+
const TurnstileMissingTokenPublic = z
|
|
411
|
+
.object({
|
|
412
|
+
code: z
|
|
413
|
+
.literal("turnstile/missing_token")
|
|
414
|
+
.describe("The request carried no Turnstile response token where one was required."),
|
|
415
|
+
status: z.literal(400).describe("Bad Request — the humanity-check token is absent."),
|
|
416
|
+
...publicFields,
|
|
417
|
+
})
|
|
418
|
+
.describe("A required Turnstile token was missing from the request (400).");
|
|
419
|
+
|
|
420
|
+
const TurnstileFailedPublic = z
|
|
421
|
+
.object({
|
|
422
|
+
code: z
|
|
423
|
+
.literal("turnstile/failed")
|
|
424
|
+
.describe("The Turnstile token did not pass siteverify, or the check could not complete (fail closed)."),
|
|
425
|
+
status: z.literal(403).describe("Forbidden — the humanity check did not pass; the request is denied."),
|
|
426
|
+
...publicFields,
|
|
427
|
+
})
|
|
428
|
+
.describe("A Turnstile humanity check failed or could not complete (403).");
|
|
429
|
+
|
|
430
|
+
const TurnstileConfigPublic = z
|
|
431
|
+
.object({
|
|
432
|
+
code: z
|
|
433
|
+
.literal("turnstile/config")
|
|
434
|
+
.describe("The Turnstile middleware is misconfigured — the secret-key binding is missing or empty."),
|
|
435
|
+
status: z.literal(500).describe("Internal Server Error — a configuration fault, not a client request."),
|
|
436
|
+
...publicFields,
|
|
437
|
+
})
|
|
438
|
+
.describe("The Turnstile middleware is misconfigured (500).");
|
|
439
|
+
|
|
440
|
+
// --- @pithy-sh/audit: audit-trail recorder codes ---
|
|
441
|
+
|
|
442
|
+
const AuditInvalidEventPublic = z
|
|
443
|
+
.object({
|
|
444
|
+
code: z
|
|
445
|
+
.literal("audit/invalid_event")
|
|
446
|
+
.describe("An audit event failed validation against the AuditEvent schema (bad action, outcome, or actor)."),
|
|
447
|
+
status: z.literal(400).describe("Bad Request — the event did not match its schema."),
|
|
448
|
+
...publicFields,
|
|
449
|
+
})
|
|
450
|
+
.describe("An audit event failed validation against its schema (400).");
|
|
451
|
+
|
|
452
|
+
const AuditWriteFailedPublic = z
|
|
453
|
+
.object({
|
|
454
|
+
code: z
|
|
455
|
+
.literal("audit/write_failed")
|
|
456
|
+
.describe(
|
|
457
|
+
"Persisting an audit event to D1 failed after retries; the failure is logged, never thrown to a client.",
|
|
458
|
+
),
|
|
459
|
+
status: z.literal(500).describe("Internal Server Error — the audit write could not be durably persisted."),
|
|
460
|
+
...publicFields,
|
|
461
|
+
})
|
|
462
|
+
.describe("Persisting an audit event failed (500). Recorded non-fatally; never breaks the audited action.");
|
|
463
|
+
|
|
464
|
+
// --- @pithy-sh/media: configurable media storage + AI enrichment codes ---
|
|
465
|
+
|
|
466
|
+
const MediaNotFoundPublic = z
|
|
467
|
+
.object({
|
|
468
|
+
code: z.literal("media/not_found").describe("A requested media record does not exist or was soft-deleted."),
|
|
469
|
+
status: z.literal(404).describe("Not Found."),
|
|
470
|
+
...publicFields,
|
|
471
|
+
})
|
|
472
|
+
.describe("A requested media record does not exist (404).");
|
|
473
|
+
|
|
474
|
+
const MediaUnsupportedPublic = z
|
|
475
|
+
.object({
|
|
476
|
+
code: z
|
|
477
|
+
.literal("media/unsupported")
|
|
478
|
+
.describe("The media type, extension, or backend is not supported for the requested operation."),
|
|
479
|
+
status: z.literal(400).describe("Bad Request — the operation does not apply to this media."),
|
|
480
|
+
...publicFields,
|
|
481
|
+
})
|
|
482
|
+
.describe("The media type or format is unsupported for this operation (400).");
|
|
483
|
+
|
|
484
|
+
const MediaStorageFailedPublic = z
|
|
485
|
+
.object({
|
|
486
|
+
code: z
|
|
487
|
+
.literal("media/storage_failed")
|
|
488
|
+
.describe("A storage operation failed — minting a direct-upload URL, or reading/deleting a stored object."),
|
|
489
|
+
status: z.literal(502).describe("Bad Gateway — the upstream storage backend returned an error."),
|
|
490
|
+
...publicFields,
|
|
491
|
+
})
|
|
492
|
+
.describe("A media storage operation failed (502).");
|
|
493
|
+
|
|
494
|
+
const MediaEnrichmentFailedPublic = z
|
|
495
|
+
.object({
|
|
496
|
+
code: z
|
|
497
|
+
.literal("media/enrichment_failed")
|
|
498
|
+
.describe("An AI enrichment step (image-to-text, transcription, or extraction) failed."),
|
|
499
|
+
status: z.literal(500).describe("Internal Server Error — an enrichment step could not complete."),
|
|
500
|
+
...publicFields,
|
|
501
|
+
})
|
|
502
|
+
.describe("A media AI-enrichment step failed (500).");
|
|
503
|
+
|
|
504
|
+
// --- @pithy-sh/leaderboard: optional ranking capability codes ---
|
|
505
|
+
|
|
506
|
+
const LeaderboardBoardNotFoundPublic = z
|
|
507
|
+
.object({
|
|
508
|
+
code: z
|
|
509
|
+
.literal("leaderboard/board_not_found")
|
|
510
|
+
.describe("No board with that key is configured. Board keys come from `pithy.config.ts`, not from the database."),
|
|
511
|
+
status: z.literal(404).describe("Not Found."),
|
|
512
|
+
...publicFields,
|
|
513
|
+
})
|
|
514
|
+
.describe("A requested board key is not configured (404).");
|
|
515
|
+
|
|
516
|
+
const LeaderboardEntryNotFoundPublic = z
|
|
517
|
+
.object({
|
|
518
|
+
code: z
|
|
519
|
+
.literal("leaderboard/entry_not_found")
|
|
520
|
+
.describe("The player has no entry on this board in this window — they have never submitted, or it was removed."),
|
|
521
|
+
status: z.literal(404).describe("Not Found."),
|
|
522
|
+
...publicFields,
|
|
523
|
+
})
|
|
524
|
+
.describe("A player has no entry on this board and window (404).");
|
|
525
|
+
|
|
526
|
+
const LeaderboardScoreRejectedPublic = z
|
|
527
|
+
.object({
|
|
528
|
+
code: z
|
|
529
|
+
.literal("leaderboard/score_rejected")
|
|
530
|
+
.describe("The submitted score failed the board's server-side `min`/`max` validation — the anti-cheat baseline."),
|
|
531
|
+
status: z.literal(400).describe("Bad Request — the score is outside the board's configured bounds."),
|
|
532
|
+
...publicFields,
|
|
533
|
+
})
|
|
534
|
+
.describe("A submitted score failed the board's bounds validation (400).");
|
|
535
|
+
|
|
536
|
+
const LeaderboardSubmitForbiddenPublic = z
|
|
537
|
+
.object({
|
|
538
|
+
code: z
|
|
539
|
+
.literal("leaderboard/submit_forbidden")
|
|
540
|
+
.describe(
|
|
541
|
+
"The caller lacks the board's submit scope. Server-authoritative writes are the default, so a player's own token cannot post a score.",
|
|
542
|
+
),
|
|
543
|
+
status: z.literal(403).describe("Forbidden — the session is authenticated but not trusted to write scores."),
|
|
544
|
+
...publicFields,
|
|
545
|
+
})
|
|
546
|
+
.describe("The caller may not submit scores to this board (403).");
|
|
547
|
+
|
|
548
|
+
const LeaderboardBoardImmutablePublic = z
|
|
549
|
+
.object({
|
|
550
|
+
code: z
|
|
551
|
+
.literal("leaderboard/board_immutable")
|
|
552
|
+
.describe(
|
|
553
|
+
"A board's `direction`, `aggregation`, or `window` changed after entries existed. Those fields are immutable across every vendor surveyed, and changing one would silently reinterpret stored scores.",
|
|
554
|
+
),
|
|
555
|
+
status: z.literal(409).describe("Conflict — the config contradicts the recorded board definition."),
|
|
556
|
+
...publicFields,
|
|
557
|
+
})
|
|
558
|
+
.describe("A board's immutable definition changed after it started recording entries (409).");
|
|
559
|
+
|
|
560
|
+
const LeaderboardInvalidSchedulePublic = z
|
|
561
|
+
.object({
|
|
562
|
+
code: z
|
|
563
|
+
.literal("leaderboard/invalid_schedule")
|
|
564
|
+
.describe("A board's `window` CRON expression is malformed or never fires, so no window key could be derived."),
|
|
565
|
+
status: z.literal(500).describe("Internal Server Error — the adopter's config is invalid, not the request."),
|
|
566
|
+
...publicFields,
|
|
567
|
+
})
|
|
568
|
+
.describe("A board's window CRON schedule is invalid (500).");
|
|
569
|
+
|
|
570
|
+
// --- @pithy-sh/multiplayer: authoritative session capability codes ---
|
|
571
|
+
|
|
572
|
+
const MultiplayerGameNotFoundPublic = z
|
|
573
|
+
.object({
|
|
574
|
+
code: z
|
|
575
|
+
.literal("multiplayer/game_not_found")
|
|
576
|
+
.describe("No game with that key is configured. Game keys come from `pithy.config.ts`, not from the database."),
|
|
577
|
+
status: z.literal(404).describe("Not Found."),
|
|
578
|
+
...publicFields,
|
|
579
|
+
})
|
|
580
|
+
.describe("A requested game key is not configured (404).");
|
|
581
|
+
|
|
582
|
+
const MultiplayerSessionNotFoundPublic = z
|
|
583
|
+
.object({
|
|
584
|
+
code: z
|
|
585
|
+
.literal("multiplayer/session_not_found")
|
|
586
|
+
.describe("No session with that id exists, or it has been evicted. Session ids address a Durable Object."),
|
|
587
|
+
status: z.literal(404).describe("Not Found."),
|
|
588
|
+
...publicFields,
|
|
589
|
+
})
|
|
590
|
+
.describe("A requested session does not exist (404).");
|
|
591
|
+
|
|
592
|
+
const MultiplayerNotAMemberPublic = z
|
|
593
|
+
.object({
|
|
594
|
+
code: z
|
|
595
|
+
.literal("multiplayer/not_a_member")
|
|
596
|
+
.describe(
|
|
597
|
+
"The authenticated player is not a member of this session. Membership binds to the AuthContext user id, never to a client-supplied id.",
|
|
598
|
+
),
|
|
599
|
+
status: z.literal(403).describe("Forbidden — authenticated, but not a participant in this session."),
|
|
600
|
+
...publicFields,
|
|
601
|
+
})
|
|
602
|
+
.describe("The caller is not a member of this session (403).");
|
|
603
|
+
|
|
604
|
+
const MultiplayerSessionFullPublic = z
|
|
605
|
+
.object({
|
|
606
|
+
code: z
|
|
607
|
+
.literal("multiplayer/session_full")
|
|
608
|
+
.describe("The session already has its full roster; no further players may join."),
|
|
609
|
+
status: z.literal(409).describe("Conflict — the session's seats are taken."),
|
|
610
|
+
...publicFields,
|
|
611
|
+
})
|
|
612
|
+
.describe("The session roster is full (409).");
|
|
613
|
+
|
|
614
|
+
const MultiplayerInvalidTransitionPublic = z
|
|
615
|
+
.object({
|
|
616
|
+
code: z
|
|
617
|
+
.literal("multiplayer/invalid_transition")
|
|
618
|
+
.describe(
|
|
619
|
+
"The requested action is not legal in the session's current phase — committing outside the commit phase, committing twice, or acting on a terminal (resolved/abandoned) session.",
|
|
620
|
+
),
|
|
621
|
+
status: z.literal(409).describe("Conflict — the action contradicts the session's current state."),
|
|
622
|
+
...publicFields,
|
|
623
|
+
})
|
|
624
|
+
.describe("An action is illegal in the session's current phase (409).");
|
|
625
|
+
|
|
626
|
+
const MultiplayerInvalidMovePublic = z
|
|
627
|
+
.object({
|
|
628
|
+
code: z
|
|
629
|
+
.literal("multiplayer/invalid_move")
|
|
630
|
+
.describe(
|
|
631
|
+
"A committed move failed the game's server-side validation — wrong shape, an unknown move, or a count outside the game's rules. The server never trusts or persists it.",
|
|
632
|
+
),
|
|
633
|
+
status: z.literal(400).describe("Bad Request — the move is malformed or against the game's rules."),
|
|
634
|
+
...publicFields,
|
|
635
|
+
})
|
|
636
|
+
.describe("A committed move failed the game's server-side validation (400).");
|
|
637
|
+
|
|
638
|
+
// --- @pithy-sh/ledger: balance-ledger capability codes ---
|
|
639
|
+
|
|
640
|
+
const LedgerCurrencyNotFoundPublic = z
|
|
641
|
+
.object({
|
|
642
|
+
code: z
|
|
643
|
+
.literal("ledger/currency_not_found")
|
|
644
|
+
.describe(
|
|
645
|
+
"No currency with that code is configured. Currency codes come from `pithy.config.ts`, not the database.",
|
|
646
|
+
),
|
|
647
|
+
status: z.literal(404).describe("Not Found."),
|
|
648
|
+
...publicFields,
|
|
649
|
+
})
|
|
650
|
+
.describe("A requested currency code is not configured (404).");
|
|
651
|
+
|
|
652
|
+
const LedgerAccountNotFoundPublic = z
|
|
653
|
+
.object({
|
|
654
|
+
code: z
|
|
655
|
+
.literal("ledger/account_not_found")
|
|
656
|
+
.describe("The player has no account in this currency yet — no funds have ever moved through it."),
|
|
657
|
+
status: z.literal(404).describe("Not Found."),
|
|
658
|
+
...publicFields,
|
|
659
|
+
})
|
|
660
|
+
.describe("A player has no account in this currency (404).");
|
|
661
|
+
|
|
662
|
+
const LedgerHoldNotFoundPublic = z
|
|
663
|
+
.object({
|
|
664
|
+
code: z.literal("ledger/hold_not_found").describe("No hold with that reference exists."),
|
|
665
|
+
status: z.literal(404).describe("Not Found."),
|
|
666
|
+
...publicFields,
|
|
667
|
+
})
|
|
668
|
+
.describe("A referenced hold does not exist (404).");
|
|
669
|
+
|
|
670
|
+
const LedgerInsufficientFundsPublic = z
|
|
671
|
+
.object({
|
|
672
|
+
code: z
|
|
673
|
+
.literal("ledger/insufficient_funds")
|
|
674
|
+
.describe(
|
|
675
|
+
"The account's available balance cannot cover the debit or hold. The overdraft guard rejected it — no funds moved.",
|
|
676
|
+
),
|
|
677
|
+
status: z.literal(409).describe("Conflict — the balance cannot cover the request."),
|
|
678
|
+
...publicFields,
|
|
679
|
+
})
|
|
680
|
+
.describe("An account cannot cover a debit or hold (409).");
|
|
681
|
+
|
|
682
|
+
const LedgerHoldNotOpenPublic = z
|
|
683
|
+
.object({
|
|
684
|
+
code: z
|
|
685
|
+
.literal("ledger/hold_not_open")
|
|
686
|
+
.describe("The hold has already been released or captured, so it cannot be resolved again."),
|
|
687
|
+
status: z.literal(409).describe("Conflict — the hold is already in a terminal state."),
|
|
688
|
+
...publicFields,
|
|
689
|
+
})
|
|
690
|
+
.describe("A hold that is already resolved cannot be resolved again (409).");
|
|
691
|
+
|
|
692
|
+
const LedgerInvalidAmountPublic = z
|
|
693
|
+
.object({
|
|
694
|
+
code: z
|
|
695
|
+
.literal("ledger/invalid_amount")
|
|
696
|
+
.describe(
|
|
697
|
+
"An amount was not a positive integer in the currency's minor unit. Amounts are never zero, negative, or fractional.",
|
|
698
|
+
),
|
|
699
|
+
status: z.literal(400).describe("Bad Request — the amount is not a positive integer."),
|
|
700
|
+
...publicFields,
|
|
701
|
+
})
|
|
702
|
+
.describe("An amount is not a positive integer (400).");
|
|
703
|
+
|
|
704
|
+
// --- @pithy-sh/rating: skill-rating + experience tracker codes ---
|
|
705
|
+
|
|
706
|
+
const RatingUnknownAlgorithmPublic = z
|
|
707
|
+
.object({
|
|
708
|
+
code: z
|
|
709
|
+
.literal("rating/unknown_algorithm")
|
|
710
|
+
.describe(
|
|
711
|
+
"A game wires a rating algorithm id that no one registered. Algorithm ids come from the built-ins (`elo`/`glicko`/`trueskill`) or `registerRatingAlgorithm`, not the database.",
|
|
712
|
+
),
|
|
713
|
+
status: z.literal(400).describe("Bad Request — the adopter's config names an unregistered algorithm."),
|
|
714
|
+
...publicFields,
|
|
715
|
+
})
|
|
716
|
+
.describe("A game references an unregistered rating algorithm (400).");
|
|
717
|
+
|
|
718
|
+
const RatingUnsupportedPlayerCountPublic = z
|
|
719
|
+
.object({
|
|
720
|
+
code: z
|
|
721
|
+
.literal("rating/unsupported_player_count")
|
|
722
|
+
.describe(
|
|
723
|
+
"A game's roster or team format is outside the algorithm's support — e.g. a 1v1-only algorithm (`elo`/`glicko`) wired to an N-player game, or a team format on an algorithm that cannot rate teams.",
|
|
724
|
+
),
|
|
725
|
+
status: z.literal(400).describe("Bad Request — the algorithm cannot rate this game's shape."),
|
|
726
|
+
...publicFields,
|
|
727
|
+
})
|
|
728
|
+
.describe("An algorithm cannot rate a game's player count or team format (400).");
|
|
729
|
+
|
|
730
|
+
const RatingInvalidParamsPublic = z
|
|
731
|
+
.object({
|
|
732
|
+
code: z
|
|
733
|
+
.literal("rating/invalid_params")
|
|
734
|
+
.describe("A game's `algoParams` block failed the chosen algorithm's own validation."),
|
|
735
|
+
status: z.literal(400).describe("Bad Request — the algorithm's tuning block is invalid."),
|
|
736
|
+
...publicFields,
|
|
737
|
+
})
|
|
738
|
+
.describe("A game's algorithm parameters failed validation (400).");
|
|
739
|
+
|
|
740
|
+
const RatingGameNotFoundPublic = z
|
|
741
|
+
.object({
|
|
742
|
+
code: z
|
|
743
|
+
.literal("rating/game_not_found")
|
|
744
|
+
.describe("No game with that key is configured. Game keys come from `pithy.config.ts`, not the database."),
|
|
745
|
+
status: z.literal(404).describe("Not Found."),
|
|
746
|
+
...publicFields,
|
|
747
|
+
})
|
|
748
|
+
.describe("A requested rating game key is not configured (404).");
|
|
749
|
+
|
|
750
|
+
const RatingPoolNotFoundPublic = z
|
|
751
|
+
.object({
|
|
752
|
+
code: z.literal("rating/pool_not_found").describe("No rating pool with that name is configured by any game."),
|
|
753
|
+
status: z.literal(404).describe("Not Found."),
|
|
754
|
+
...publicFields,
|
|
755
|
+
})
|
|
756
|
+
.describe("A requested rating pool is not configured (404).");
|
|
757
|
+
|
|
758
|
+
const RatingRecordForbiddenPublic = z
|
|
759
|
+
.object({
|
|
760
|
+
code: z
|
|
761
|
+
.literal("rating/record_forbidden")
|
|
762
|
+
.describe(
|
|
763
|
+
"Recording a game's outcome requires the server-authoritative record scope. The authenticated caller does not hold it.",
|
|
764
|
+
),
|
|
765
|
+
status: z.literal(403).describe("Forbidden — the caller may not record outcomes."),
|
|
766
|
+
...publicFields,
|
|
767
|
+
})
|
|
768
|
+
.describe("The caller lacks the scope to record a rated outcome (403).");
|
|
769
|
+
|
|
770
|
+
// --- @pithy-sh/matchmaking: pairing + presence capability codes ---
|
|
771
|
+
|
|
772
|
+
const MatchmakingRoomNotFoundPublic = z
|
|
773
|
+
.object({
|
|
774
|
+
code: z
|
|
775
|
+
.literal("matchmaking/room_not_found")
|
|
776
|
+
.describe("No open room has that code — it never existed, expired, or was already used up."),
|
|
777
|
+
status: z.literal(404).describe("Not Found."),
|
|
778
|
+
...publicFields,
|
|
779
|
+
})
|
|
780
|
+
.describe("A room code resolves to no open room (404).");
|
|
781
|
+
|
|
782
|
+
const MatchmakingRoomFullPublic = z
|
|
783
|
+
.object({
|
|
784
|
+
code: z
|
|
785
|
+
.literal("matchmaking/room_full")
|
|
786
|
+
.describe("The room's code has already been redeemed its maximum number of times."),
|
|
787
|
+
status: z.literal(409).describe("Conflict — the room code is spent."),
|
|
788
|
+
...publicFields,
|
|
789
|
+
})
|
|
790
|
+
.describe("A room code has no redemptions left (409).");
|
|
791
|
+
|
|
792
|
+
const MatchmakingInvalidCodePublic = z
|
|
793
|
+
.object({
|
|
794
|
+
code: z
|
|
795
|
+
.literal("matchmaking/invalid_code")
|
|
796
|
+
.describe("The supplied room code is malformed — wrong shape or length."),
|
|
797
|
+
status: z.literal(400).describe("Bad Request — the code is not a valid room code."),
|
|
798
|
+
...publicFields,
|
|
799
|
+
})
|
|
800
|
+
.describe("A supplied room code is malformed (400).");
|
|
801
|
+
|
|
802
|
+
const MatchmakingInviteNotFoundPublic = z
|
|
803
|
+
.object({
|
|
804
|
+
code: z
|
|
805
|
+
.literal("matchmaking/invite_not_found")
|
|
806
|
+
.describe("No pending invite with that id exists, or it was already accepted, declined, or expired."),
|
|
807
|
+
status: z.literal(404).describe("Not Found."),
|
|
808
|
+
...publicFields,
|
|
809
|
+
})
|
|
810
|
+
.describe("A requested invite does not exist (404).");
|
|
811
|
+
|
|
812
|
+
const MatchmakingInviteForbiddenPublic = z
|
|
813
|
+
.object({
|
|
814
|
+
code: z
|
|
815
|
+
.literal("matchmaking/invite_forbidden")
|
|
816
|
+
.describe("The authenticated user is neither the inviter nor the invitee of this invite."),
|
|
817
|
+
status: z.literal(403).describe("Forbidden — the invite is not yours to act on."),
|
|
818
|
+
...publicFields,
|
|
819
|
+
})
|
|
820
|
+
.describe("The caller may not act on this invite (403).");
|
|
821
|
+
|
|
822
|
+
const MatchmakingUserNotFoundPublic = z
|
|
823
|
+
.object({
|
|
824
|
+
code: z
|
|
825
|
+
.literal("matchmaking/user_not_found")
|
|
826
|
+
.describe(
|
|
827
|
+
"The invited email or screen name resolves to no single user — no match, or an ambiguous display name. Invite by email for a unique identity.",
|
|
828
|
+
),
|
|
829
|
+
status: z.literal(404).describe("Not Found."),
|
|
830
|
+
...publicFields,
|
|
831
|
+
})
|
|
832
|
+
.describe("An invite target resolves to no single user (404).");
|
|
833
|
+
|
|
834
|
+
const MatchmakingAlreadyFriendsPublic = z
|
|
835
|
+
.object({
|
|
836
|
+
code: z
|
|
837
|
+
.literal("matchmaking/already_friends")
|
|
838
|
+
.describe("The two users are already connected, or a request between them is already pending."),
|
|
839
|
+
status: z.literal(409).describe("Conflict — the friendship or request already exists."),
|
|
840
|
+
...publicFields,
|
|
841
|
+
})
|
|
842
|
+
.describe("A friend connection or request already exists (409).");
|
|
843
|
+
|
|
844
|
+
const MatchmakingFriendRequestNotFoundPublic = z
|
|
845
|
+
.object({
|
|
846
|
+
code: z
|
|
847
|
+
.literal("matchmaking/friend_request_not_found")
|
|
848
|
+
.describe("No pending friend request exists between these users for the caller to accept or decline."),
|
|
849
|
+
status: z.literal(404).describe("Not Found."),
|
|
850
|
+
...publicFields,
|
|
851
|
+
})
|
|
852
|
+
.describe("No pending friend request exists (404).");
|
|
853
|
+
|
|
854
|
+
const MatchmakingNotQueuedPublic = z
|
|
855
|
+
.object({
|
|
856
|
+
code: z
|
|
857
|
+
.literal("matchmaking/not_queued")
|
|
858
|
+
.describe("The player is not currently waiting in this game's matchmaking queue."),
|
|
859
|
+
status: z.literal(404).describe("Not Found."),
|
|
860
|
+
...publicFields,
|
|
861
|
+
})
|
|
862
|
+
.describe("The player is not in the queue (404).");
|
|
863
|
+
|
|
864
|
+
// --- @pithy-sh/storage: object store, quota, and share-link codes ---
|
|
865
|
+
|
|
866
|
+
const StorageNotFoundPublic = z
|
|
867
|
+
.object({
|
|
868
|
+
code: z
|
|
869
|
+
.literal("storage/not_found")
|
|
870
|
+
.describe("No object row matches, or the object exists but is not visible to this caller."),
|
|
871
|
+
status: z.literal(404).describe("Not Found."),
|
|
872
|
+
...publicFields,
|
|
873
|
+
})
|
|
874
|
+
.describe("The object does not exist, or is not visible to the caller (404).");
|
|
875
|
+
|
|
876
|
+
const StorageForbiddenPublic = z
|
|
877
|
+
.object({
|
|
878
|
+
code: z.literal("storage/forbidden").describe("The caller does not own the object and the object is not public."),
|
|
879
|
+
status: z.literal(403).describe("Forbidden."),
|
|
880
|
+
...publicFields,
|
|
881
|
+
})
|
|
882
|
+
.describe("The caller is not the owner and the object is not public (403).");
|
|
883
|
+
|
|
884
|
+
const StorageQuotaExceededPublic = z
|
|
885
|
+
.object({
|
|
886
|
+
code: z
|
|
887
|
+
.literal("storage/quota_exceeded")
|
|
888
|
+
.describe(
|
|
889
|
+
"The declared upload size would push the owner past their byte quota. Checked at init against pending and stored rows together, so concurrent inits cannot race past the limit.",
|
|
890
|
+
),
|
|
891
|
+
status: z.literal(413).describe("Content Too Large."),
|
|
892
|
+
...publicFields,
|
|
893
|
+
})
|
|
894
|
+
.describe("The upload would exceed the owner's byte quota (413).");
|
|
895
|
+
|
|
896
|
+
const StorageUploadIncompletePublic = z
|
|
897
|
+
.object({
|
|
898
|
+
code: z
|
|
899
|
+
.literal("storage/upload_incomplete")
|
|
900
|
+
.describe("The object is still pending — its bytes have not been uploaded, so it cannot be read or copied."),
|
|
901
|
+
status: z.literal(409).describe("Conflict."),
|
|
902
|
+
...publicFields,
|
|
903
|
+
})
|
|
904
|
+
.describe("The object's upload has not completed (409).");
|
|
905
|
+
|
|
906
|
+
const StorageMultipartFailedPublic = z
|
|
907
|
+
.object({
|
|
908
|
+
code: z
|
|
909
|
+
.literal("storage/multipart_failed")
|
|
910
|
+
.describe("R2 rejected the multipart completion — most often an ETag that does not match the uploaded part."),
|
|
911
|
+
status: z.literal(500).describe("Internal Server Error."),
|
|
912
|
+
...publicFields,
|
|
913
|
+
})
|
|
914
|
+
.describe("A multipart upload could not be completed (500).");
|
|
915
|
+
|
|
916
|
+
const StorageShareExpiredPublic = z
|
|
917
|
+
.object({
|
|
918
|
+
code: z.literal("storage/share_expired").describe("The share token is past its expiry."),
|
|
919
|
+
status: z.literal(410).describe("Gone."),
|
|
920
|
+
...publicFields,
|
|
921
|
+
})
|
|
922
|
+
.describe("The share token has expired (410).");
|
|
923
|
+
|
|
924
|
+
const StorageShareRevokedPublic = z
|
|
925
|
+
.object({
|
|
926
|
+
code: z
|
|
927
|
+
.literal("storage/share_revoked")
|
|
928
|
+
.describe(
|
|
929
|
+
"The share token was revoked. A presigned URL cannot be revoked, only expired — which is why shares are rows.",
|
|
930
|
+
),
|
|
931
|
+
status: z.literal(410).describe("Gone."),
|
|
932
|
+
...publicFields,
|
|
933
|
+
})
|
|
934
|
+
.describe("The share token has been revoked (410).");
|
|
935
|
+
|
|
936
|
+
// --- @pithy-sh/vector: index, metadata, and query-boundary codes ---
|
|
937
|
+
|
|
938
|
+
const VectorMetadataIndexDriftPublic = z
|
|
939
|
+
.object({
|
|
940
|
+
code: z
|
|
941
|
+
.literal("vector/metadata_index_drift")
|
|
942
|
+
.describe(
|
|
943
|
+
"A field declared filterable has no live metadata index. Fatal at boot: Vectorize does not error on such a filter, it silently omits every vector written before the index existed.",
|
|
944
|
+
),
|
|
945
|
+
status: z.literal(500).describe("Internal Server Error."),
|
|
946
|
+
...publicFields,
|
|
947
|
+
})
|
|
948
|
+
.describe("A declared filterable field has no live metadata index (500).");
|
|
949
|
+
|
|
950
|
+
const VectorDimensionMismatchPublic = z
|
|
951
|
+
.object({
|
|
952
|
+
code: z
|
|
953
|
+
.literal("vector/dimension_mismatch")
|
|
954
|
+
.describe("The supplied vector's length does not match the index's configured dimensions."),
|
|
955
|
+
status: z.literal(400).describe("Bad Request."),
|
|
956
|
+
...publicFields,
|
|
957
|
+
})
|
|
958
|
+
.describe("The vector length does not match the index dimensions (400).");
|
|
959
|
+
|
|
960
|
+
const VectorTopKExceededPublic = z
|
|
961
|
+
.object({
|
|
962
|
+
code: z
|
|
963
|
+
.literal("vector/topk_exceeded")
|
|
964
|
+
.describe("topK is above Vectorize's ceiling — 50 when values or metadata are returned, 100 when neither is."),
|
|
965
|
+
status: z.literal(400).describe("Bad Request."),
|
|
966
|
+
...publicFields,
|
|
967
|
+
})
|
|
968
|
+
.describe("topK exceeds the limit for this query mode (400).");
|
|
969
|
+
|
|
970
|
+
const VectorFilterTooLargePublic = z
|
|
971
|
+
.object({
|
|
972
|
+
code: z
|
|
973
|
+
.literal("vector/filter_too_large")
|
|
974
|
+
.describe("The filter's compact JSON representation reaches Vectorize's 2,048-byte ceiling."),
|
|
975
|
+
status: z.literal(400).describe("Bad Request."),
|
|
976
|
+
...publicFields,
|
|
977
|
+
})
|
|
978
|
+
.describe("The metadata filter is too large (400).");
|
|
979
|
+
|
|
980
|
+
const VectorMetadataTooLargePublic = z
|
|
981
|
+
.object({
|
|
982
|
+
code: z
|
|
983
|
+
.literal("vector/metadata_too_large")
|
|
984
|
+
.describe(
|
|
985
|
+
"One vector's metadata reaches Vectorize's 10 KiB ceiling. Long content belongs in D1, not in metadata.",
|
|
986
|
+
),
|
|
987
|
+
status: z.literal(400).describe("Bad Request."),
|
|
988
|
+
...publicFields,
|
|
989
|
+
})
|
|
990
|
+
.describe("A vector's metadata exceeds the size limit (400).");
|
|
991
|
+
|
|
992
|
+
const VectorIndexNotFoundPublic = z
|
|
993
|
+
.object({
|
|
994
|
+
code: z
|
|
995
|
+
.literal("vector/index_not_found")
|
|
996
|
+
.describe("The named index is not present in the vector capability's configuration."),
|
|
997
|
+
status: z.literal(404).describe("Not Found."),
|
|
998
|
+
...publicFields,
|
|
999
|
+
})
|
|
1000
|
+
.describe("The named index is not configured (404).");
|
|
1001
|
+
|
|
1002
|
+
const VectorUnfilterableFieldPublic = z
|
|
1003
|
+
.object({
|
|
1004
|
+
code: z
|
|
1005
|
+
.literal("vector/unfilterable_field")
|
|
1006
|
+
.describe(
|
|
1007
|
+
"A filter names a field that is not marked filterable. The runtime guard behind the compile-time type error, for untyped callers.",
|
|
1008
|
+
),
|
|
1009
|
+
status: z.literal(400).describe("Bad Request."),
|
|
1010
|
+
...publicFields,
|
|
1011
|
+
})
|
|
1012
|
+
.describe("The filter names a field with no metadata index (400).");
|
|
1013
|
+
|
|
1014
|
+
// --- @pithy-sh/payments: purchase, entitlement, and rail codes ---
|
|
1015
|
+
|
|
1016
|
+
const PaymentsInvalidReceiptPublic = z
|
|
1017
|
+
.object({
|
|
1018
|
+
code: z
|
|
1019
|
+
.literal("payments/invalid_receipt")
|
|
1020
|
+
.describe(
|
|
1021
|
+
"The submitted receipt or transaction is malformed — not a JWS, not a token the rail issues, or missing the fields the rail's own format requires. Nothing was asked of the provider.",
|
|
1022
|
+
),
|
|
1023
|
+
status: z.literal(400).describe("Bad Request — the receipt could not be read."),
|
|
1024
|
+
...publicFields,
|
|
1025
|
+
})
|
|
1026
|
+
.describe("A submitted receipt is malformed (400).");
|
|
1027
|
+
|
|
1028
|
+
const PaymentsVerificationFailedPublic = z
|
|
1029
|
+
.object({
|
|
1030
|
+
code: z
|
|
1031
|
+
.literal("payments/verification_failed")
|
|
1032
|
+
.describe(
|
|
1033
|
+
"The rail was asked and said no: a signature that does not verify against the provider's keys, or a transaction the provider does not acknowledge. Distinct from `provider_unavailable`, which is the rail failing to answer at all.",
|
|
1034
|
+
),
|
|
1035
|
+
status: z.literal(400).describe("Bad Request — the provider rejected the receipt."),
|
|
1036
|
+
...publicFields,
|
|
1037
|
+
})
|
|
1038
|
+
.describe("The provider rejected the receipt (400).");
|
|
1039
|
+
|
|
1040
|
+
const PaymentsWebhookUnverifiedPublic = z
|
|
1041
|
+
.object({
|
|
1042
|
+
code: z
|
|
1043
|
+
.literal("payments/webhook_unverified")
|
|
1044
|
+
.describe(
|
|
1045
|
+
"An inbound provider notification failed its authenticity check — Apple's JWS chain, Google's OIDC token, or Stripe's HMAC. Separate from `verification_failed` because this is the `signed-webhook` strategy denying an unauthenticated caller, not a verdict about a purchase.",
|
|
1046
|
+
),
|
|
1047
|
+
status: z.literal(401).describe("Unauthorized — the notification did not prove it came from the provider."),
|
|
1048
|
+
...publicFields,
|
|
1049
|
+
})
|
|
1050
|
+
.describe("An inbound webhook failed signature verification (401).");
|
|
1051
|
+
|
|
1052
|
+
const PaymentsRailNotConfiguredPublic = z
|
|
1053
|
+
.object({
|
|
1054
|
+
code: z
|
|
1055
|
+
.literal("payments/rail_not_configured")
|
|
1056
|
+
.describe(
|
|
1057
|
+
"The request names a payment rail this project has not enabled. Rails come from `pithy.config.ts`, not the database — so a Stripe checkout on an Apple-only project is a missing resource, not a malformed request.",
|
|
1058
|
+
),
|
|
1059
|
+
status: z.literal(404).describe("Not Found — the rail is not enabled for this project."),
|
|
1060
|
+
...publicFields,
|
|
1061
|
+
})
|
|
1062
|
+
.describe("A named rail is not enabled (404).");
|
|
1063
|
+
|
|
1064
|
+
const PaymentsReconcileNotProvisionedPublic = z
|
|
1065
|
+
.object({
|
|
1066
|
+
code: z
|
|
1067
|
+
.literal("payments/reconcile_not_provisioned")
|
|
1068
|
+
.describe(
|
|
1069
|
+
"A reconciliation pass was asked for on a deployment whose Workflow host is not deployed or not bound. The route exists and the caller's scope was right, so this is a provisioning fact rather than a bad request — and the fix is a deploy the adopter performs.",
|
|
1070
|
+
),
|
|
1071
|
+
status: z.literal(501).describe("Not Implemented — this deployment cannot start a pass yet."),
|
|
1072
|
+
...publicFields,
|
|
1073
|
+
})
|
|
1074
|
+
.describe("Reconciliation has no Workflow host here (501).");
|
|
1075
|
+
|
|
1076
|
+
const PaymentsProductNotFoundPublic = z
|
|
1077
|
+
.object({
|
|
1078
|
+
code: z
|
|
1079
|
+
.literal("payments/product_not_found")
|
|
1080
|
+
.describe(
|
|
1081
|
+
"No catalog product maps the rail's SKU or price id. The catalog lives in `pithy.config.ts` and is deliberately not runtime-mutable, so a new SKU needs a deploy.",
|
|
1082
|
+
),
|
|
1083
|
+
status: z.literal(404).describe("Not Found — no catalog product maps that SKU."),
|
|
1084
|
+
...publicFields,
|
|
1085
|
+
})
|
|
1086
|
+
.describe("A provider SKU maps to no catalog product (404).");
|
|
1087
|
+
|
|
1088
|
+
const PaymentsEnvironmentMismatchPublic = z
|
|
1089
|
+
.object({
|
|
1090
|
+
code: z
|
|
1091
|
+
.literal("payments/environment_mismatch")
|
|
1092
|
+
.describe(
|
|
1093
|
+
"A sandbox purchase reached a production deployment, or the reverse. Rejected outright: granting a real entitlement from a sandbox transaction is the most common in-app-purchase security defect, so the environment travels with every purchase and never widens.",
|
|
1094
|
+
),
|
|
1095
|
+
status: z.literal(400).describe("Bad Request — the purchase belongs to a different environment."),
|
|
1096
|
+
...publicFields,
|
|
1097
|
+
})
|
|
1098
|
+
.describe("A purchase belongs to a different store environment (400).");
|
|
1099
|
+
|
|
1100
|
+
const PaymentsReceiptAlreadyOwnedPublic = z
|
|
1101
|
+
.object({
|
|
1102
|
+
code: z
|
|
1103
|
+
.literal("payments/receipt_already_owned")
|
|
1104
|
+
.describe(
|
|
1105
|
+
"The transaction is already projected against a different user. A replay by its own owner is a 200 — the write path is idempotent — but a receipt lifted from another account is refused rather than silently rebound, which is what makes a stolen receipt worthless.",
|
|
1106
|
+
),
|
|
1107
|
+
status: z.literal(409).describe("Conflict — the transaction belongs to another user."),
|
|
1108
|
+
...publicFields,
|
|
1109
|
+
})
|
|
1110
|
+
.describe("A submitted transaction belongs to another user (409).");
|
|
1111
|
+
|
|
1112
|
+
const PaymentsProviderUnavailablePublic = z
|
|
1113
|
+
.object({
|
|
1114
|
+
code: z
|
|
1115
|
+
.literal("payments/provider_unavailable")
|
|
1116
|
+
.describe(
|
|
1117
|
+
"The rail could not be reached or answered with a server error. The purchase is neither granted nor refused — the reconciliation Workflow repairs it, so the caller may retry.",
|
|
1118
|
+
),
|
|
1119
|
+
status: z.literal(503).describe("Service Unavailable — the provider did not answer."),
|
|
1120
|
+
...publicFields,
|
|
1121
|
+
})
|
|
1122
|
+
.describe("The provider could not be reached (503).");
|
|
1123
|
+
|
|
1124
|
+
const PaymentsEntitlementRequiredPublic = z
|
|
1125
|
+
.object({
|
|
1126
|
+
code: z
|
|
1127
|
+
.literal("payments/entitlement_required")
|
|
1128
|
+
.describe(
|
|
1129
|
+
"The caller does not hold an entitlement the route requires. Also what a gate returns when no entitlement provider is composed at all — the seam fails closed, and which of the two it was is in `detail`, never in the response.",
|
|
1130
|
+
),
|
|
1131
|
+
status: z.literal(403).describe("Forbidden — the caller is not entitled."),
|
|
1132
|
+
...publicFields,
|
|
1133
|
+
})
|
|
1134
|
+
.describe("The caller lacks a required entitlement (403).");
|
|
1135
|
+
|
|
1136
|
+
const PaymentsClawbackFailedPublic = z
|
|
1137
|
+
.object({
|
|
1138
|
+
code: z
|
|
1139
|
+
.literal("payments/clawback_failed")
|
|
1140
|
+
.describe(
|
|
1141
|
+
"A refund's clawback debit was refused because the balance no longer covers it. The refusal is correct — routing around it would mean either a negative balance or a silent write-off — so the refund still stands and this is the record of the shortfall, usually written to the audit trail rather than returned to a caller.",
|
|
1142
|
+
),
|
|
1143
|
+
status: z.literal(409).describe("Conflict — the balance cannot cover the reversal."),
|
|
1144
|
+
...publicFields,
|
|
1145
|
+
})
|
|
1146
|
+
.describe("A refund could not be clawed back from a spent balance (409).");
|
|
1147
|
+
|
|
1148
|
+
const PaymentsDiscountInvalidPublic = z
|
|
1149
|
+
.object({
|
|
1150
|
+
code: z
|
|
1151
|
+
.literal("payments/discount_invalid")
|
|
1152
|
+
.describe(
|
|
1153
|
+
"A discount code the store would not accept — unknown, expired, exhausted, or not valid for what is being bought. Its own code rather than a generic checkout failure, because a customer told 'something went wrong' concludes their card was declined and stops trying, where one told their code was not accepted removes it and buys.",
|
|
1154
|
+
),
|
|
1155
|
+
status: z.literal(400).describe("Bad Request — the store refused the code, not the payment."),
|
|
1156
|
+
...publicFields,
|
|
1157
|
+
})
|
|
1158
|
+
.describe("A discount code the store would not accept (400).");
|
|
1159
|
+
|
|
1160
|
+
const PaymentsEntitlementNotInCatalogPublic = z
|
|
1161
|
+
.object({
|
|
1162
|
+
code: z
|
|
1163
|
+
.literal("payments/entitlement_not_in_catalog")
|
|
1164
|
+
.describe(
|
|
1165
|
+
"A control-plane grant named an entitlement key this project does not define — no product lists it and the adopter did not declare it grantable by hand. 400 rather than 404 because a grant names a vocabulary, not a resource: `pr` for `pro` is a malformed request, and left unchecked it is a success, a row, and a customer who stays locked out.",
|
|
1166
|
+
),
|
|
1167
|
+
status: z.literal(400).describe("Bad Request — no product grants that key and none was declared."),
|
|
1168
|
+
...publicFields,
|
|
1169
|
+
})
|
|
1170
|
+
.describe("A manual grant named an entitlement key the catalog does not define (400).");
|
|
1171
|
+
|
|
1172
|
+
const PaymentsSubjectUnresolvedPublic = z
|
|
1173
|
+
.object({
|
|
1174
|
+
code: z
|
|
1175
|
+
.literal("payments/subject_unresolved")
|
|
1176
|
+
.describe(
|
|
1177
|
+
"A payments write path — submitting a purchase, restoring, opening checkout, opening the billing portal — reached the capability with no answer to which subject the caller acts for. Under organization billing that answer comes from the adopter's own resolver, because the capability never learns what an organization is. 403 rather than 400 or 500: the caller is authenticated and the request is well-formed, and an operator reading our logs cannot fix a seam of the adopter's that returned nothing. On a read an unanswered seam holds nothing and the gate denies, which needs no code; on a write there is nothing to write the row against, and doing nothing quietly is a purchase that disappears.",
|
|
1178
|
+
),
|
|
1179
|
+
status: z.literal(403).describe("Forbidden — the caller is known, but who they are acting for is not."),
|
|
1180
|
+
...publicFields,
|
|
1181
|
+
})
|
|
1182
|
+
.describe("A write path could not resolve which subject the caller acts for (403).");
|
|
1183
|
+
|
|
1184
|
+
const PaymentsSubscriptionChangeRefusedPublic = z
|
|
1185
|
+
.object({
|
|
1186
|
+
code: z
|
|
1187
|
+
.literal("payments/subscription_change_refused")
|
|
1188
|
+
.describe(
|
|
1189
|
+
"A subscription write — a plan change, a cancellation, or the withdrawal of a scheduled one — that the subscription's own state forbids, not the request. Three states reach it. A subscription carrying more than one item, or a quantity above one, because a store's update replaces the items array whole: sending back only the item the caller named would silently delete the rest, and inventing a quantity would re-price a line nobody asked about, so a shape the rail cannot reproduce exactly is refused rather than guessed at. A subscription already ended, which has nothing left to change. And a request that contradicts the state it arrived at — moving the plan on a subscription whose cancellation is already scheduled, where honoring one instruction means discarding the other. 409 rather than 400 because the request is well-formed and the same request against the same subscription in another state would be accepted, and rather than 500 because nothing has failed: refusing is the correct answer and the caller's move is to re-read the subscription, not to re-word the call. Which of the three fired is in `detail` and never in the response, because the branch names the store's identifiers, the item count and the price ids on them — the shape of somebody's billing. A request whose outcome already holds is not this code: a change to the plan already held, or a cancellation already scheduled, is a success that calls no provider, which is what makes a retried write safe.",
|
|
1190
|
+
),
|
|
1191
|
+
status: z.literal(409).describe("Conflict — the subscription's present state forbids the change asked of it."),
|
|
1192
|
+
...publicFields,
|
|
1193
|
+
})
|
|
1194
|
+
.describe("A subscription's state forbids the change asked of it (409).");
|
|
1195
|
+
|
|
1196
|
+
// --- @pithy-sh/core: the `control-plane` verification strategy ---
|
|
1197
|
+
//
|
|
1198
|
+
// NOT Cloudflare's control plane. Everywhere else in this repo "control plane" means the Cloudflare
|
|
1199
|
+
// REST API `@pithy-sh/cloudflare` calls to provision D1, KV, and Workers — outbound, CF-authenticated,
|
|
1200
|
+
// provisioning, and carrying the `cloudflare/*` codes above. These are the inverse: **inbound** calls
|
|
1201
|
+
// from a management client the adopter registered, authenticated by a key the adopter trusts. Two
|
|
1202
|
+
// meanings, one word, one tree. See `docs/CONTROL-PLANE.md`.
|
|
1203
|
+
|
|
1204
|
+
const ControlPlaneNotConnectedPublic = z
|
|
1205
|
+
.object({
|
|
1206
|
+
code: z
|
|
1207
|
+
.literal("controlplane/not_connected")
|
|
1208
|
+
.describe(
|
|
1209
|
+
"No management-client connection is registered for this environment. The shipped default: the seam is present and denies everything until the adopter provisions a credential. There is no backdoor.",
|
|
1210
|
+
),
|
|
1211
|
+
status: z.literal(403).describe("Forbidden."),
|
|
1212
|
+
...publicFields,
|
|
1213
|
+
})
|
|
1214
|
+
.describe("No control-plane connection is registered for this environment (403).");
|
|
1215
|
+
|
|
1216
|
+
const ControlPlaneInvalidCredentialPublic = z
|
|
1217
|
+
.object({
|
|
1218
|
+
code: z
|
|
1219
|
+
.literal("controlplane/invalid_credential")
|
|
1220
|
+
.describe(
|
|
1221
|
+
"The presented credential failed verification. Deliberately one code for every failing step — malformed token, unknown `kid`, bad signature, wrong `aud`, wrong environment, expired, outside clock skew, replayed `jti`, mismatched body digest — so a caller cannot use the response to learn which check it tripped. `detail` names the step for the log, and the HTTP codec strips it.",
|
|
1222
|
+
),
|
|
1223
|
+
status: z.literal(401).describe("Unauthorized."),
|
|
1224
|
+
...publicFields,
|
|
1225
|
+
})
|
|
1226
|
+
.describe("The control-plane credential failed verification (401).");
|
|
1227
|
+
|
|
1228
|
+
const ControlPlaneInsufficientScopePublic = z
|
|
1229
|
+
.object({
|
|
1230
|
+
code: z
|
|
1231
|
+
.literal("controlplane/insufficient_scope")
|
|
1232
|
+
.describe(
|
|
1233
|
+
"The credential verified, but the token's scope does not cover this operation, or the connection was never granted it. Scopes are stored and enforced on the adopter's side — enforced only by the caller, they would not be a limit at all. Default deny on anything unscoped.",
|
|
1234
|
+
),
|
|
1235
|
+
status: z.literal(403).describe("Forbidden."),
|
|
1236
|
+
...publicFields,
|
|
1237
|
+
})
|
|
1238
|
+
.describe("The control-plane credential lacks the scope this operation requires (403).");
|
|
1239
|
+
|
|
1240
|
+
const ControlPlaneKeyNotFoundPublic = z
|
|
1241
|
+
.object({
|
|
1242
|
+
code: z
|
|
1243
|
+
.literal("controlplane/key_not_found")
|
|
1244
|
+
.describe("The `keyId` named for expiry is not registered on this connection."),
|
|
1245
|
+
status: z.literal(404).describe("Not Found."),
|
|
1246
|
+
...publicFields,
|
|
1247
|
+
})
|
|
1248
|
+
.describe("No registered key answers to that key id (404).");
|
|
1249
|
+
|
|
1250
|
+
const ControlPlaneKeyConflictPublic = z
|
|
1251
|
+
.object({
|
|
1252
|
+
code: z
|
|
1253
|
+
.literal("controlplane/key_conflict")
|
|
1254
|
+
.describe(
|
|
1255
|
+
"A key operation conflicts with the connection's current state: the `keyId` is already registered, or expiring it would leave the connection with no live key. The second case is the lockout this seam exists to make impossible — a connection whose every key is expired has no authenticated path back.",
|
|
1256
|
+
),
|
|
1257
|
+
status: z.literal(409).describe("Conflict."),
|
|
1258
|
+
...publicFields,
|
|
1259
|
+
})
|
|
1260
|
+
.describe("A key operation conflicts with the connection's current state (409).");
|
|
1261
|
+
|
|
1262
|
+
// --- @pithy-sh/support: inbound inbox, taxonomy, and classification codes ---
|
|
1263
|
+
|
|
1264
|
+
const SupportNotFoundPublic = z
|
|
1265
|
+
.object({
|
|
1266
|
+
code: z.literal("support/not_found").describe("A requested support thread, message, or attachment does not exist."),
|
|
1267
|
+
status: z.literal(404).describe("Not Found."),
|
|
1268
|
+
...publicFields,
|
|
1269
|
+
})
|
|
1270
|
+
.describe("A requested support thread does not exist (404).");
|
|
1271
|
+
|
|
1272
|
+
const SupportInvalidCategoryPublic = z
|
|
1273
|
+
.object({
|
|
1274
|
+
code: z
|
|
1275
|
+
.literal("support/invalid_category")
|
|
1276
|
+
.describe(
|
|
1277
|
+
"A support category key or its description failed validation. Raised by `defineSupportCategories` at author time, so a typo fails where the constant is written rather than the first time a model happens to return it.",
|
|
1278
|
+
),
|
|
1279
|
+
status: z.literal(400).describe("Bad Request — the taxonomy declaration is malformed."),
|
|
1280
|
+
...publicFields,
|
|
1281
|
+
})
|
|
1282
|
+
.describe("A support category declaration is invalid (400).");
|
|
1283
|
+
|
|
1284
|
+
const SupportUnparseableMessagePublic = z
|
|
1285
|
+
.object({
|
|
1286
|
+
code: z
|
|
1287
|
+
.literal("support/unparseable_message")
|
|
1288
|
+
.describe(
|
|
1289
|
+
"An inbound message could not be read as email, or carried no sender to thread and reply to. Expected traffic on a public address rather than an exceptional condition — inbound mail is attacker-controlled.",
|
|
1290
|
+
),
|
|
1291
|
+
status: z.literal(400).describe("Bad Request — the message was not usable as email."),
|
|
1292
|
+
...publicFields,
|
|
1293
|
+
})
|
|
1294
|
+
.describe("An inbound support message could not be parsed (400).");
|
|
1295
|
+
|
|
1296
|
+
const SupportRejectedPublic = z
|
|
1297
|
+
.object({
|
|
1298
|
+
code: z
|
|
1299
|
+
.literal("support/rejected")
|
|
1300
|
+
.describe(
|
|
1301
|
+
"An inbound message was refused by the guard before anything was persisted — over the size bound, or over a per-sender or global rate bound. A public support address is a public write endpoint into the adopter's D1, and this is the code that says the bound held.",
|
|
1302
|
+
),
|
|
1303
|
+
status: z.literal(429).describe("Too Many Requests — a size or rate bound refused the message."),
|
|
1304
|
+
...publicFields,
|
|
1305
|
+
})
|
|
1306
|
+
.describe("An inbound support message was refused by the volume guard (429).");
|
|
1307
|
+
|
|
1308
|
+
const SupportClassificationFailedPublic = z
|
|
1309
|
+
.object({
|
|
1310
|
+
code: z
|
|
1311
|
+
.literal("support/classification_failed")
|
|
1312
|
+
.describe(
|
|
1313
|
+
"The Workers AI classification step could not complete — the binding is absent, or the model was unavailable. A model that answers badly is not this: an unusable answer becomes `uncategorized`, because a thread with no classification is a legitimate state and a retry budget should not burn on a model that will say the same thing again.",
|
|
1314
|
+
),
|
|
1315
|
+
status: z.literal(500).describe("Internal Server Error — classification could not run."),
|
|
1316
|
+
...publicFields,
|
|
1317
|
+
})
|
|
1318
|
+
.describe("A support classification step failed (500).");
|
|
1319
|
+
|
|
1320
|
+
const SupportReplyFailedPublic = z
|
|
1321
|
+
.object({
|
|
1322
|
+
code: z
|
|
1323
|
+
.literal("support/reply_failed")
|
|
1324
|
+
.describe(
|
|
1325
|
+
"A reply could not be enqueued: `@pithy-sh/email` is not composed, or it refused the job. Support never sends mail itself — every reply goes through the email capability's durable send path so it carries the adopter's domain, their DKIM, and the threading headers that keep the customer's view of the conversation intact.",
|
|
1326
|
+
),
|
|
1327
|
+
status: z.literal(502).describe("Bad Gateway — the send path refused or is absent."),
|
|
1328
|
+
...publicFields,
|
|
1329
|
+
})
|
|
1330
|
+
.describe("A support reply could not be enqueued (502).");
|
|
1331
|
+
// --- @pithy-sh/testers: cohort, roster, opt-in, and nudge codes ---
|
|
1332
|
+
|
|
1333
|
+
const TestersCohortNotFoundPublic = z
|
|
1334
|
+
.object({
|
|
1335
|
+
code: z
|
|
1336
|
+
.literal("testers/cohort_not_found")
|
|
1337
|
+
.describe(
|
|
1338
|
+
"No cohort answers to that id. Cohort ids are opaque and server-generated, so a miss is a typo or a deleted cohort.",
|
|
1339
|
+
),
|
|
1340
|
+
status: z.literal(404).describe("Not Found."),
|
|
1341
|
+
...publicFields,
|
|
1342
|
+
})
|
|
1343
|
+
.describe("No cohort answers to that id (404).");
|
|
1344
|
+
|
|
1345
|
+
const TestersMemberNotFoundPublic = z
|
|
1346
|
+
.object({
|
|
1347
|
+
code: z.literal("testers/member_not_found").describe("No member of that cohort answers to that id or address."),
|
|
1348
|
+
status: z.literal(404).describe("Not Found."),
|
|
1349
|
+
...publicFields,
|
|
1350
|
+
})
|
|
1351
|
+
.describe("No roster member answers to that id or address (404).");
|
|
1352
|
+
|
|
1353
|
+
const TestersInvalidTokenPublic = z
|
|
1354
|
+
.object({
|
|
1355
|
+
code: z
|
|
1356
|
+
.literal("testers/invalid_token")
|
|
1357
|
+
.describe(
|
|
1358
|
+
"The opt-in link could not be honored. Deliberately one code for every failing step — malformed shape, a token matching no member, a member who has withdrawn or been removed, or an invitation older than the configured link lifetime — so someone holding a guess cannot use the response to learn which check it tripped, nor whether the tester they named exists. `detail` names the step for the log, and the HTTP codec strips it.",
|
|
1359
|
+
),
|
|
1360
|
+
status: z.literal(400).describe("Bad Request — the link could not be honored."),
|
|
1361
|
+
...publicFields,
|
|
1362
|
+
})
|
|
1363
|
+
.describe("An opt-in link failed verification (400).");
|
|
1364
|
+
|
|
1365
|
+
const TestersRosterFullPublic = z
|
|
1366
|
+
.object({
|
|
1367
|
+
code: z
|
|
1368
|
+
.literal("testers/roster_full")
|
|
1369
|
+
.describe(
|
|
1370
|
+
"The cohort is at its roster cap. Raise `maxRosterSize` on the cohort, or remove a lapsed member first.",
|
|
1371
|
+
),
|
|
1372
|
+
status: z.literal(409).describe("Conflict."),
|
|
1373
|
+
...publicFields,
|
|
1374
|
+
})
|
|
1375
|
+
.describe("The cohort's roster is at its cap (409).");
|
|
1376
|
+
|
|
1377
|
+
const TestersAlreadyOnRosterPublic = z
|
|
1378
|
+
.object({
|
|
1379
|
+
code: z
|
|
1380
|
+
.literal("testers/already_on_roster")
|
|
1381
|
+
.describe(
|
|
1382
|
+
"That address is already on the cohort's roster in a live state. Re-inviting is not an error worth swallowing: the caller either meant `resend`, which preserves the existing member's history, or is about to lose the opt-in date that the whole streak rests on.",
|
|
1383
|
+
),
|
|
1384
|
+
status: z.literal(409).describe("Conflict."),
|
|
1385
|
+
...publicFields,
|
|
1386
|
+
})
|
|
1387
|
+
.describe("That address is already on the roster (409).");
|
|
1388
|
+
|
|
1389
|
+
const TestersNudgeCooldownPublic = z
|
|
1390
|
+
.object({
|
|
1391
|
+
code: z
|
|
1392
|
+
.literal("testers/nudge_cooldown")
|
|
1393
|
+
.describe(
|
|
1394
|
+
"Every selected tester is inside the per-tester nudge cooldown, so nothing was sent. Not a partial success: a request that mails none of its targets should say so rather than return an empty success a dashboard renders as 'sent'.",
|
|
1395
|
+
),
|
|
1396
|
+
status: z.literal(429).describe("Too Many Requests."),
|
|
1397
|
+
...publicFields,
|
|
1398
|
+
})
|
|
1399
|
+
.describe("Every selected tester is inside the nudge cooldown (429).");
|
|
1400
|
+
|
|
1401
|
+
const TestersCopyNotAllowedPublic = z
|
|
1402
|
+
.object({
|
|
1403
|
+
code: z
|
|
1404
|
+
.literal("testers/copy_not_allowed")
|
|
1405
|
+
.describe(
|
|
1406
|
+
"The caller supplied nudge copy, but this deployment pins the shipped defaults. Copy override is a deliberate per-project decision, because supplied words go out over the adopter's own DKIM signature.",
|
|
1407
|
+
),
|
|
1408
|
+
status: z.literal(403).describe("Forbidden."),
|
|
1409
|
+
...publicFields,
|
|
1410
|
+
})
|
|
1411
|
+
.describe("Caller-supplied nudge copy is disabled for this deployment (403).");
|
|
1412
|
+
|
|
1413
|
+
const TestersWithdrawnPublic = z
|
|
1414
|
+
.object({
|
|
1415
|
+
code: z
|
|
1416
|
+
.literal("testers/withdrawn")
|
|
1417
|
+
.describe(
|
|
1418
|
+
"That address withdrew from this cohort itself, and a withdrawal is the tester's own decision rather than a roster state the developer may reverse. Re-inviting them would restart the chase against somebody who asked to be left alone, so it is refused. Removing a tester is the developer's own act and stays reversible; this is not.",
|
|
1419
|
+
),
|
|
1420
|
+
status: z.literal(409).describe("Conflict — the tester's own decision forbids this, not the request."),
|
|
1421
|
+
...publicFields,
|
|
1422
|
+
})
|
|
1423
|
+
.describe("An invitation to an address that withdrew from this cohort (409).");
|
|
1424
|
+
|
|
1425
|
+
const TestersCohortClosedPublic = z
|
|
1426
|
+
.object({
|
|
1427
|
+
code: z
|
|
1428
|
+
.literal("testers/cohort_closed")
|
|
1429
|
+
.describe(
|
|
1430
|
+
"The cohort is closed. A closed cohort keeps its history and stays readable, but stops accruing snapshots and stops sending — so inviting onto it, chasing it, or running a pass over it are all refused rather than quietly honored.",
|
|
1431
|
+
),
|
|
1432
|
+
status: z.literal(409).describe("Conflict — the cohort's state forbids this, not the request."),
|
|
1433
|
+
...publicFields,
|
|
1434
|
+
})
|
|
1435
|
+
.describe("An operation that would write to or send from a closed cohort (409).");
|
|
1436
|
+
|
|
1437
|
+
const TestersNotConfiguredPublic = z
|
|
1438
|
+
.object({
|
|
1439
|
+
code: z
|
|
1440
|
+
.literal("testers/not_configured")
|
|
1441
|
+
.describe(
|
|
1442
|
+
"The capability is composed but cannot complete this operation: no `baseUrl` to build an opt-in link from, or a required binding is absent. An adopter-fixable wiring gap rather than a bug, so it names the missing piece in its action line.",
|
|
1443
|
+
),
|
|
1444
|
+
status: z.literal(500).describe("Internal Server Error — the deployment is incomplete."),
|
|
1445
|
+
...publicFields,
|
|
1446
|
+
})
|
|
1447
|
+
.describe("The testers capability is composed but incompletely configured (500).");
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
* The public projection of every error **the kit itself defines**: the wire shape clients receive.
|
|
1451
|
+
* No `detail`. Parsing strips any stray `detail` (Zod drops unknown keys), so this schema is itself
|
|
1452
|
+
* a guard against internal context leaking outward. `PublicErrorPayload` below widens it by the
|
|
1453
|
+
* adopter seam; this one stays closed, because the code set is what makes it discriminated.
|
|
1454
|
+
*/
|
|
1455
|
+
export const KitPublicErrorPayload = z
|
|
1456
|
+
.discriminatedUnion("code", [
|
|
1457
|
+
InvalidInputPublic,
|
|
1458
|
+
InvalidTokenPublic,
|
|
1459
|
+
ForbiddenPublic,
|
|
1460
|
+
AuthProviderUnavailablePublic,
|
|
1461
|
+
NotFoundPublic,
|
|
1462
|
+
ConflictPublic,
|
|
1463
|
+
RateLimitPublic,
|
|
1464
|
+
InternalPublic,
|
|
1465
|
+
UpstreamFailedPublic,
|
|
1466
|
+
UpstreamTimeoutPublic,
|
|
1467
|
+
InvalidWorkflowParamsPublic,
|
|
1468
|
+
MissingWorkflowBindingPublic,
|
|
1469
|
+
UnknownWorkflowPublic,
|
|
1470
|
+
WorkflowFailedPublic,
|
|
1471
|
+
WebhookUnverifiedPublic,
|
|
1472
|
+
CloudflareNotConfiguredPublic,
|
|
1473
|
+
CloudflareRequestFailedPublic,
|
|
1474
|
+
CloudflareInvalidResponsePublic,
|
|
1475
|
+
SecretNotFoundPublic,
|
|
1476
|
+
SecretAlreadyExistsPublic,
|
|
1477
|
+
SecretInvalidValuePublic,
|
|
1478
|
+
SecretCryptoFailedPublic,
|
|
1479
|
+
SecretRotationUnrecordedPublic,
|
|
1480
|
+
SecretRotationUnsupportedPublic,
|
|
1481
|
+
EmailTemplateNotFoundPublic,
|
|
1482
|
+
EmailInvalidPayloadPublic,
|
|
1483
|
+
EmailInvalidTokenPublic,
|
|
1484
|
+
EmailSuppressedPublic,
|
|
1485
|
+
EmailRateLimitedPublic,
|
|
1486
|
+
EmailSendFailedPublic,
|
|
1487
|
+
TurnstileMissingTokenPublic,
|
|
1488
|
+
TurnstileFailedPublic,
|
|
1489
|
+
TurnstileConfigPublic,
|
|
1490
|
+
AuditInvalidEventPublic,
|
|
1491
|
+
AuditWriteFailedPublic,
|
|
1492
|
+
MediaNotFoundPublic,
|
|
1493
|
+
MediaUnsupportedPublic,
|
|
1494
|
+
MediaStorageFailedPublic,
|
|
1495
|
+
MediaEnrichmentFailedPublic,
|
|
1496
|
+
LeaderboardBoardNotFoundPublic,
|
|
1497
|
+
LeaderboardEntryNotFoundPublic,
|
|
1498
|
+
LeaderboardScoreRejectedPublic,
|
|
1499
|
+
LeaderboardSubmitForbiddenPublic,
|
|
1500
|
+
LeaderboardBoardImmutablePublic,
|
|
1501
|
+
LeaderboardInvalidSchedulePublic,
|
|
1502
|
+
MultiplayerGameNotFoundPublic,
|
|
1503
|
+
MultiplayerSessionNotFoundPublic,
|
|
1504
|
+
MultiplayerNotAMemberPublic,
|
|
1505
|
+
MultiplayerSessionFullPublic,
|
|
1506
|
+
MultiplayerInvalidTransitionPublic,
|
|
1507
|
+
MultiplayerInvalidMovePublic,
|
|
1508
|
+
LedgerCurrencyNotFoundPublic,
|
|
1509
|
+
LedgerAccountNotFoundPublic,
|
|
1510
|
+
LedgerHoldNotFoundPublic,
|
|
1511
|
+
LedgerInsufficientFundsPublic,
|
|
1512
|
+
LedgerHoldNotOpenPublic,
|
|
1513
|
+
LedgerInvalidAmountPublic,
|
|
1514
|
+
RatingUnknownAlgorithmPublic,
|
|
1515
|
+
RatingUnsupportedPlayerCountPublic,
|
|
1516
|
+
RatingInvalidParamsPublic,
|
|
1517
|
+
RatingGameNotFoundPublic,
|
|
1518
|
+
RatingPoolNotFoundPublic,
|
|
1519
|
+
RatingRecordForbiddenPublic,
|
|
1520
|
+
MatchmakingRoomNotFoundPublic,
|
|
1521
|
+
MatchmakingRoomFullPublic,
|
|
1522
|
+
MatchmakingInvalidCodePublic,
|
|
1523
|
+
MatchmakingInviteNotFoundPublic,
|
|
1524
|
+
MatchmakingInviteForbiddenPublic,
|
|
1525
|
+
MatchmakingUserNotFoundPublic,
|
|
1526
|
+
MatchmakingAlreadyFriendsPublic,
|
|
1527
|
+
MatchmakingFriendRequestNotFoundPublic,
|
|
1528
|
+
MatchmakingNotQueuedPublic,
|
|
1529
|
+
StorageNotFoundPublic,
|
|
1530
|
+
StorageForbiddenPublic,
|
|
1531
|
+
StorageQuotaExceededPublic,
|
|
1532
|
+
StorageUploadIncompletePublic,
|
|
1533
|
+
StorageMultipartFailedPublic,
|
|
1534
|
+
StorageShareExpiredPublic,
|
|
1535
|
+
StorageShareRevokedPublic,
|
|
1536
|
+
VectorMetadataIndexDriftPublic,
|
|
1537
|
+
VectorDimensionMismatchPublic,
|
|
1538
|
+
VectorTopKExceededPublic,
|
|
1539
|
+
VectorFilterTooLargePublic,
|
|
1540
|
+
VectorMetadataTooLargePublic,
|
|
1541
|
+
VectorIndexNotFoundPublic,
|
|
1542
|
+
VectorUnfilterableFieldPublic,
|
|
1543
|
+
PaymentsInvalidReceiptPublic,
|
|
1544
|
+
PaymentsVerificationFailedPublic,
|
|
1545
|
+
PaymentsWebhookUnverifiedPublic,
|
|
1546
|
+
PaymentsRailNotConfiguredPublic,
|
|
1547
|
+
PaymentsReconcileNotProvisionedPublic,
|
|
1548
|
+
PaymentsProductNotFoundPublic,
|
|
1549
|
+
PaymentsEnvironmentMismatchPublic,
|
|
1550
|
+
PaymentsReceiptAlreadyOwnedPublic,
|
|
1551
|
+
PaymentsProviderUnavailablePublic,
|
|
1552
|
+
PaymentsEntitlementRequiredPublic,
|
|
1553
|
+
PaymentsClawbackFailedPublic,
|
|
1554
|
+
PaymentsDiscountInvalidPublic,
|
|
1555
|
+
PaymentsEntitlementNotInCatalogPublic,
|
|
1556
|
+
PaymentsSubjectUnresolvedPublic,
|
|
1557
|
+
PaymentsSubscriptionChangeRefusedPublic,
|
|
1558
|
+
ControlPlaneNotConnectedPublic,
|
|
1559
|
+
ControlPlaneInvalidCredentialPublic,
|
|
1560
|
+
ControlPlaneInsufficientScopePublic,
|
|
1561
|
+
ControlPlaneKeyNotFoundPublic,
|
|
1562
|
+
ControlPlaneKeyConflictPublic,
|
|
1563
|
+
SupportNotFoundPublic,
|
|
1564
|
+
SupportInvalidCategoryPublic,
|
|
1565
|
+
SupportUnparseableMessagePublic,
|
|
1566
|
+
SupportRejectedPublic,
|
|
1567
|
+
SupportClassificationFailedPublic,
|
|
1568
|
+
SupportReplyFailedPublic,
|
|
1569
|
+
TestersCohortNotFoundPublic,
|
|
1570
|
+
TestersMemberNotFoundPublic,
|
|
1571
|
+
TestersInvalidTokenPublic,
|
|
1572
|
+
TestersRosterFullPublic,
|
|
1573
|
+
TestersAlreadyOnRosterPublic,
|
|
1574
|
+
TestersNudgeCooldownPublic,
|
|
1575
|
+
TestersCopyNotAllowedPublic,
|
|
1576
|
+
TestersCohortClosedPublic,
|
|
1577
|
+
TestersWithdrawnPublic,
|
|
1578
|
+
TestersNotConfiguredPublic,
|
|
1579
|
+
])
|
|
1580
|
+
.describe("The public shape of every error the kit defines — the closed set, safe for the wire.");
|
|
1581
|
+
|
|
1582
|
+
// The full members: each public member plus the operator-only `action` and `detail`. Built as explicit consts
|
|
1583
|
+
// (not mapped) so the `code`-literal discriminated-union types survive for `Extract` narrowing.
|
|
1584
|
+
const InvalidInput = InvalidInputPublic.extend(internalFields).describe(InvalidInputPublic.description ?? "");
|
|
1585
|
+
const InvalidToken = InvalidTokenPublic.extend(internalFields).describe(InvalidTokenPublic.description ?? "");
|
|
1586
|
+
const Forbidden = ForbiddenPublic.extend(internalFields).describe(ForbiddenPublic.description ?? "");
|
|
1587
|
+
const AuthProviderUnavailable = AuthProviderUnavailablePublic.extend(internalFields).describe(
|
|
1588
|
+
AuthProviderUnavailablePublic.description ?? "",
|
|
1589
|
+
);
|
|
1590
|
+
const NotFound = NotFoundPublic.extend(internalFields).describe(NotFoundPublic.description ?? "");
|
|
1591
|
+
const Conflict = ConflictPublic.extend(internalFields).describe(ConflictPublic.description ?? "");
|
|
1592
|
+
const RateLimit = RateLimitPublic.extend(internalFields).describe(RateLimitPublic.description ?? "");
|
|
1593
|
+
const Internal = InternalPublic.extend(internalFields).describe(InternalPublic.description ?? "");
|
|
1594
|
+
const UpstreamFailed = UpstreamFailedPublic.extend(internalFields).describe(UpstreamFailedPublic.description ?? "");
|
|
1595
|
+
const UpstreamTimeout = UpstreamTimeoutPublic.extend(internalFields).describe(UpstreamTimeoutPublic.description ?? "");
|
|
1596
|
+
const InvalidWorkflowParams = InvalidWorkflowParamsPublic.extend(internalFields).describe(
|
|
1597
|
+
InvalidWorkflowParamsPublic.description ?? "",
|
|
1598
|
+
);
|
|
1599
|
+
const MissingWorkflowBinding = MissingWorkflowBindingPublic.extend(internalFields).describe(
|
|
1600
|
+
MissingWorkflowBindingPublic.description ?? "",
|
|
1601
|
+
);
|
|
1602
|
+
const UnknownWorkflow = UnknownWorkflowPublic.extend(internalFields).describe(UnknownWorkflowPublic.description ?? "");
|
|
1603
|
+
const WorkflowFailed = WorkflowFailedPublic.extend(internalFields).describe(WorkflowFailedPublic.description ?? "");
|
|
1604
|
+
const WebhookUnverified = WebhookUnverifiedPublic.extend(internalFields).describe(
|
|
1605
|
+
WebhookUnverifiedPublic.description ?? "",
|
|
1606
|
+
);
|
|
1607
|
+
const CloudflareNotConfigured = CloudflareNotConfiguredPublic.extend(internalFields).describe(
|
|
1608
|
+
CloudflareNotConfiguredPublic.description ?? "",
|
|
1609
|
+
);
|
|
1610
|
+
const CloudflareRequestFailed = CloudflareRequestFailedPublic.extend(internalFields).describe(
|
|
1611
|
+
CloudflareRequestFailedPublic.description ?? "",
|
|
1612
|
+
);
|
|
1613
|
+
const CloudflareInvalidResponse = CloudflareInvalidResponsePublic.extend(internalFields).describe(
|
|
1614
|
+
CloudflareInvalidResponsePublic.description ?? "",
|
|
1615
|
+
);
|
|
1616
|
+
const SecretNotFound = SecretNotFoundPublic.extend(internalFields).describe(SecretNotFoundPublic.description ?? "");
|
|
1617
|
+
const SecretAlreadyExists = SecretAlreadyExistsPublic.extend(internalFields).describe(
|
|
1618
|
+
SecretAlreadyExistsPublic.description ?? "",
|
|
1619
|
+
);
|
|
1620
|
+
const SecretInvalidValue = SecretInvalidValuePublic.extend(internalFields).describe(
|
|
1621
|
+
SecretInvalidValuePublic.description ?? "",
|
|
1622
|
+
);
|
|
1623
|
+
const SecretCryptoFailed = SecretCryptoFailedPublic.extend(internalFields).describe(
|
|
1624
|
+
SecretCryptoFailedPublic.description ?? "",
|
|
1625
|
+
);
|
|
1626
|
+
const SecretRotationUnrecorded = SecretRotationUnrecordedPublic.extend(internalFields).describe(
|
|
1627
|
+
SecretRotationUnrecordedPublic.description ?? "",
|
|
1628
|
+
);
|
|
1629
|
+
const SecretRotationUnsupported = SecretRotationUnsupportedPublic.extend(internalFields).describe(
|
|
1630
|
+
SecretRotationUnsupportedPublic.description ?? "",
|
|
1631
|
+
);
|
|
1632
|
+
const EmailTemplateNotFound = EmailTemplateNotFoundPublic.extend(internalFields).describe(
|
|
1633
|
+
EmailTemplateNotFoundPublic.description ?? "",
|
|
1634
|
+
);
|
|
1635
|
+
const EmailInvalidPayload = EmailInvalidPayloadPublic.extend(internalFields).describe(
|
|
1636
|
+
EmailInvalidPayloadPublic.description ?? "",
|
|
1637
|
+
);
|
|
1638
|
+
const EmailInvalidToken = EmailInvalidTokenPublic.extend(internalFields).describe(
|
|
1639
|
+
EmailInvalidTokenPublic.description ?? "",
|
|
1640
|
+
);
|
|
1641
|
+
const EmailSuppressed = EmailSuppressedPublic.extend(internalFields).describe(EmailSuppressedPublic.description ?? "");
|
|
1642
|
+
const EmailRateLimited = EmailRateLimitedPublic.extend(internalFields).describe(
|
|
1643
|
+
EmailRateLimitedPublic.description ?? "",
|
|
1644
|
+
);
|
|
1645
|
+
const EmailSendFailed = EmailSendFailedPublic.extend(internalFields).describe(EmailSendFailedPublic.description ?? "");
|
|
1646
|
+
const TurnstileMissingToken = TurnstileMissingTokenPublic.extend(internalFields).describe(
|
|
1647
|
+
TurnstileMissingTokenPublic.description ?? "",
|
|
1648
|
+
);
|
|
1649
|
+
const TurnstileFailed = TurnstileFailedPublic.extend(internalFields).describe(TurnstileFailedPublic.description ?? "");
|
|
1650
|
+
const TurnstileConfig = TurnstileConfigPublic.extend(internalFields).describe(TurnstileConfigPublic.description ?? "");
|
|
1651
|
+
const AuditInvalidEvent = AuditInvalidEventPublic.extend(internalFields).describe(
|
|
1652
|
+
AuditInvalidEventPublic.description ?? "",
|
|
1653
|
+
);
|
|
1654
|
+
const AuditWriteFailed = AuditWriteFailedPublic.extend(internalFields).describe(
|
|
1655
|
+
AuditWriteFailedPublic.description ?? "",
|
|
1656
|
+
);
|
|
1657
|
+
const MediaNotFound = MediaNotFoundPublic.extend(internalFields).describe(MediaNotFoundPublic.description ?? "");
|
|
1658
|
+
const MediaUnsupported = MediaUnsupportedPublic.extend(internalFields).describe(
|
|
1659
|
+
MediaUnsupportedPublic.description ?? "",
|
|
1660
|
+
);
|
|
1661
|
+
const MediaStorageFailed = MediaStorageFailedPublic.extend(internalFields).describe(
|
|
1662
|
+
MediaStorageFailedPublic.description ?? "",
|
|
1663
|
+
);
|
|
1664
|
+
const MediaEnrichmentFailed = MediaEnrichmentFailedPublic.extend(internalFields).describe(
|
|
1665
|
+
MediaEnrichmentFailedPublic.description ?? "",
|
|
1666
|
+
);
|
|
1667
|
+
const LeaderboardBoardNotFound = LeaderboardBoardNotFoundPublic.extend(internalFields).describe(
|
|
1668
|
+
LeaderboardBoardNotFoundPublic.description ?? "",
|
|
1669
|
+
);
|
|
1670
|
+
const LeaderboardEntryNotFound = LeaderboardEntryNotFoundPublic.extend(internalFields).describe(
|
|
1671
|
+
LeaderboardEntryNotFoundPublic.description ?? "",
|
|
1672
|
+
);
|
|
1673
|
+
const LeaderboardScoreRejected = LeaderboardScoreRejectedPublic.extend(internalFields).describe(
|
|
1674
|
+
LeaderboardScoreRejectedPublic.description ?? "",
|
|
1675
|
+
);
|
|
1676
|
+
const LeaderboardSubmitForbidden = LeaderboardSubmitForbiddenPublic.extend(internalFields).describe(
|
|
1677
|
+
LeaderboardSubmitForbiddenPublic.description ?? "",
|
|
1678
|
+
);
|
|
1679
|
+
const LeaderboardBoardImmutable = LeaderboardBoardImmutablePublic.extend(internalFields).describe(
|
|
1680
|
+
LeaderboardBoardImmutablePublic.description ?? "",
|
|
1681
|
+
);
|
|
1682
|
+
const LeaderboardInvalidSchedule = LeaderboardInvalidSchedulePublic.extend(internalFields).describe(
|
|
1683
|
+
LeaderboardInvalidSchedulePublic.description ?? "",
|
|
1684
|
+
);
|
|
1685
|
+
const MultiplayerGameNotFound = MultiplayerGameNotFoundPublic.extend(internalFields).describe(
|
|
1686
|
+
MultiplayerGameNotFoundPublic.description ?? "",
|
|
1687
|
+
);
|
|
1688
|
+
const MultiplayerSessionNotFound = MultiplayerSessionNotFoundPublic.extend(internalFields).describe(
|
|
1689
|
+
MultiplayerSessionNotFoundPublic.description ?? "",
|
|
1690
|
+
);
|
|
1691
|
+
const MultiplayerNotAMember = MultiplayerNotAMemberPublic.extend(internalFields).describe(
|
|
1692
|
+
MultiplayerNotAMemberPublic.description ?? "",
|
|
1693
|
+
);
|
|
1694
|
+
const MultiplayerSessionFull = MultiplayerSessionFullPublic.extend(internalFields).describe(
|
|
1695
|
+
MultiplayerSessionFullPublic.description ?? "",
|
|
1696
|
+
);
|
|
1697
|
+
const MultiplayerInvalidTransition = MultiplayerInvalidTransitionPublic.extend(internalFields).describe(
|
|
1698
|
+
MultiplayerInvalidTransitionPublic.description ?? "",
|
|
1699
|
+
);
|
|
1700
|
+
const MultiplayerInvalidMove = MultiplayerInvalidMovePublic.extend(internalFields).describe(
|
|
1701
|
+
MultiplayerInvalidMovePublic.description ?? "",
|
|
1702
|
+
);
|
|
1703
|
+
const LedgerCurrencyNotFound = LedgerCurrencyNotFoundPublic.extend(internalFields).describe(
|
|
1704
|
+
LedgerCurrencyNotFoundPublic.description ?? "",
|
|
1705
|
+
);
|
|
1706
|
+
const LedgerAccountNotFound = LedgerAccountNotFoundPublic.extend(internalFields).describe(
|
|
1707
|
+
LedgerAccountNotFoundPublic.description ?? "",
|
|
1708
|
+
);
|
|
1709
|
+
const LedgerHoldNotFound = LedgerHoldNotFoundPublic.extend(internalFields).describe(
|
|
1710
|
+
LedgerHoldNotFoundPublic.description ?? "",
|
|
1711
|
+
);
|
|
1712
|
+
const LedgerInsufficientFunds = LedgerInsufficientFundsPublic.extend(internalFields).describe(
|
|
1713
|
+
LedgerInsufficientFundsPublic.description ?? "",
|
|
1714
|
+
);
|
|
1715
|
+
const LedgerHoldNotOpen = LedgerHoldNotOpenPublic.extend(internalFields).describe(
|
|
1716
|
+
LedgerHoldNotOpenPublic.description ?? "",
|
|
1717
|
+
);
|
|
1718
|
+
const LedgerInvalidAmount = LedgerInvalidAmountPublic.extend(internalFields).describe(
|
|
1719
|
+
LedgerInvalidAmountPublic.description ?? "",
|
|
1720
|
+
);
|
|
1721
|
+
const RatingUnknownAlgorithm = RatingUnknownAlgorithmPublic.extend(internalFields).describe(
|
|
1722
|
+
RatingUnknownAlgorithmPublic.description ?? "",
|
|
1723
|
+
);
|
|
1724
|
+
const RatingUnsupportedPlayerCount = RatingUnsupportedPlayerCountPublic.extend(internalFields).describe(
|
|
1725
|
+
RatingUnsupportedPlayerCountPublic.description ?? "",
|
|
1726
|
+
);
|
|
1727
|
+
const RatingInvalidParams = RatingInvalidParamsPublic.extend(internalFields).describe(
|
|
1728
|
+
RatingInvalidParamsPublic.description ?? "",
|
|
1729
|
+
);
|
|
1730
|
+
const RatingGameNotFound = RatingGameNotFoundPublic.extend(internalFields).describe(
|
|
1731
|
+
RatingGameNotFoundPublic.description ?? "",
|
|
1732
|
+
);
|
|
1733
|
+
const RatingPoolNotFound = RatingPoolNotFoundPublic.extend(internalFields).describe(
|
|
1734
|
+
RatingPoolNotFoundPublic.description ?? "",
|
|
1735
|
+
);
|
|
1736
|
+
const RatingRecordForbidden = RatingRecordForbiddenPublic.extend(internalFields).describe(
|
|
1737
|
+
RatingRecordForbiddenPublic.description ?? "",
|
|
1738
|
+
);
|
|
1739
|
+
const MatchmakingRoomNotFound = MatchmakingRoomNotFoundPublic.extend(internalFields).describe(
|
|
1740
|
+
MatchmakingRoomNotFoundPublic.description ?? "",
|
|
1741
|
+
);
|
|
1742
|
+
const MatchmakingRoomFull = MatchmakingRoomFullPublic.extend(internalFields).describe(
|
|
1743
|
+
MatchmakingRoomFullPublic.description ?? "",
|
|
1744
|
+
);
|
|
1745
|
+
const MatchmakingInvalidCode = MatchmakingInvalidCodePublic.extend(internalFields).describe(
|
|
1746
|
+
MatchmakingInvalidCodePublic.description ?? "",
|
|
1747
|
+
);
|
|
1748
|
+
const MatchmakingInviteNotFound = MatchmakingInviteNotFoundPublic.extend(internalFields).describe(
|
|
1749
|
+
MatchmakingInviteNotFoundPublic.description ?? "",
|
|
1750
|
+
);
|
|
1751
|
+
const MatchmakingInviteForbidden = MatchmakingInviteForbiddenPublic.extend(internalFields).describe(
|
|
1752
|
+
MatchmakingInviteForbiddenPublic.description ?? "",
|
|
1753
|
+
);
|
|
1754
|
+
const MatchmakingUserNotFound = MatchmakingUserNotFoundPublic.extend(internalFields).describe(
|
|
1755
|
+
MatchmakingUserNotFoundPublic.description ?? "",
|
|
1756
|
+
);
|
|
1757
|
+
const MatchmakingAlreadyFriends = MatchmakingAlreadyFriendsPublic.extend(internalFields).describe(
|
|
1758
|
+
MatchmakingAlreadyFriendsPublic.description ?? "",
|
|
1759
|
+
);
|
|
1760
|
+
const MatchmakingFriendRequestNotFound = MatchmakingFriendRequestNotFoundPublic.extend(internalFields).describe(
|
|
1761
|
+
MatchmakingFriendRequestNotFoundPublic.description ?? "",
|
|
1762
|
+
);
|
|
1763
|
+
const MatchmakingNotQueued = MatchmakingNotQueuedPublic.extend(internalFields).describe(
|
|
1764
|
+
MatchmakingNotQueuedPublic.description ?? "",
|
|
1765
|
+
);
|
|
1766
|
+
const StorageNotFound = StorageNotFoundPublic.extend(internalFields).describe(StorageNotFoundPublic.description ?? "");
|
|
1767
|
+
const StorageForbidden = StorageForbiddenPublic.extend(internalFields).describe(
|
|
1768
|
+
StorageForbiddenPublic.description ?? "",
|
|
1769
|
+
);
|
|
1770
|
+
const StorageQuotaExceeded = StorageQuotaExceededPublic.extend(internalFields).describe(
|
|
1771
|
+
StorageQuotaExceededPublic.description ?? "",
|
|
1772
|
+
);
|
|
1773
|
+
const StorageUploadIncomplete = StorageUploadIncompletePublic.extend(internalFields).describe(
|
|
1774
|
+
StorageUploadIncompletePublic.description ?? "",
|
|
1775
|
+
);
|
|
1776
|
+
const StorageMultipartFailed = StorageMultipartFailedPublic.extend(internalFields).describe(
|
|
1777
|
+
StorageMultipartFailedPublic.description ?? "",
|
|
1778
|
+
);
|
|
1779
|
+
const StorageShareExpired = StorageShareExpiredPublic.extend(internalFields).describe(
|
|
1780
|
+
StorageShareExpiredPublic.description ?? "",
|
|
1781
|
+
);
|
|
1782
|
+
const StorageShareRevoked = StorageShareRevokedPublic.extend(internalFields).describe(
|
|
1783
|
+
StorageShareRevokedPublic.description ?? "",
|
|
1784
|
+
);
|
|
1785
|
+
const VectorMetadataIndexDrift = VectorMetadataIndexDriftPublic.extend(internalFields).describe(
|
|
1786
|
+
VectorMetadataIndexDriftPublic.description ?? "",
|
|
1787
|
+
);
|
|
1788
|
+
const VectorDimensionMismatch = VectorDimensionMismatchPublic.extend(internalFields).describe(
|
|
1789
|
+
VectorDimensionMismatchPublic.description ?? "",
|
|
1790
|
+
);
|
|
1791
|
+
const VectorTopKExceeded = VectorTopKExceededPublic.extend(internalFields).describe(
|
|
1792
|
+
VectorTopKExceededPublic.description ?? "",
|
|
1793
|
+
);
|
|
1794
|
+
const VectorFilterTooLarge = VectorFilterTooLargePublic.extend(internalFields).describe(
|
|
1795
|
+
VectorFilterTooLargePublic.description ?? "",
|
|
1796
|
+
);
|
|
1797
|
+
const VectorMetadataTooLarge = VectorMetadataTooLargePublic.extend(internalFields).describe(
|
|
1798
|
+
VectorMetadataTooLargePublic.description ?? "",
|
|
1799
|
+
);
|
|
1800
|
+
const VectorIndexNotFound = VectorIndexNotFoundPublic.extend(internalFields).describe(
|
|
1801
|
+
VectorIndexNotFoundPublic.description ?? "",
|
|
1802
|
+
);
|
|
1803
|
+
const VectorUnfilterableField = VectorUnfilterableFieldPublic.extend(internalFields).describe(
|
|
1804
|
+
VectorUnfilterableFieldPublic.description ?? "",
|
|
1805
|
+
);
|
|
1806
|
+
const PaymentsInvalidReceipt = PaymentsInvalidReceiptPublic.extend(internalFields).describe(
|
|
1807
|
+
PaymentsInvalidReceiptPublic.description ?? "",
|
|
1808
|
+
);
|
|
1809
|
+
const PaymentsVerificationFailed = PaymentsVerificationFailedPublic.extend(internalFields).describe(
|
|
1810
|
+
PaymentsVerificationFailedPublic.description ?? "",
|
|
1811
|
+
);
|
|
1812
|
+
const PaymentsWebhookUnverified = PaymentsWebhookUnverifiedPublic.extend(internalFields).describe(
|
|
1813
|
+
PaymentsWebhookUnverifiedPublic.description ?? "",
|
|
1814
|
+
);
|
|
1815
|
+
const PaymentsRailNotConfigured = PaymentsRailNotConfiguredPublic.extend(internalFields).describe(
|
|
1816
|
+
PaymentsRailNotConfiguredPublic.description ?? "",
|
|
1817
|
+
);
|
|
1818
|
+
const PaymentsReconcileNotProvisioned = PaymentsReconcileNotProvisionedPublic.extend(internalFields).describe(
|
|
1819
|
+
PaymentsReconcileNotProvisionedPublic.description ?? "",
|
|
1820
|
+
);
|
|
1821
|
+
const PaymentsProductNotFound = PaymentsProductNotFoundPublic.extend(internalFields).describe(
|
|
1822
|
+
PaymentsProductNotFoundPublic.description ?? "",
|
|
1823
|
+
);
|
|
1824
|
+
const PaymentsEnvironmentMismatch = PaymentsEnvironmentMismatchPublic.extend(internalFields).describe(
|
|
1825
|
+
PaymentsEnvironmentMismatchPublic.description ?? "",
|
|
1826
|
+
);
|
|
1827
|
+
const PaymentsReceiptAlreadyOwned = PaymentsReceiptAlreadyOwnedPublic.extend(internalFields).describe(
|
|
1828
|
+
PaymentsReceiptAlreadyOwnedPublic.description ?? "",
|
|
1829
|
+
);
|
|
1830
|
+
const PaymentsProviderUnavailable = PaymentsProviderUnavailablePublic.extend(internalFields).describe(
|
|
1831
|
+
PaymentsProviderUnavailablePublic.description ?? "",
|
|
1832
|
+
);
|
|
1833
|
+
const PaymentsEntitlementRequired = PaymentsEntitlementRequiredPublic.extend(internalFields).describe(
|
|
1834
|
+
PaymentsEntitlementRequiredPublic.description ?? "",
|
|
1835
|
+
);
|
|
1836
|
+
const PaymentsClawbackFailed = PaymentsClawbackFailedPublic.extend(internalFields).describe(
|
|
1837
|
+
PaymentsClawbackFailedPublic.description ?? "",
|
|
1838
|
+
);
|
|
1839
|
+
const PaymentsDiscountInvalid = PaymentsDiscountInvalidPublic.extend(internalFields).describe(
|
|
1840
|
+
PaymentsDiscountInvalidPublic.description ?? "",
|
|
1841
|
+
);
|
|
1842
|
+
|
|
1843
|
+
const PaymentsEntitlementNotInCatalog = PaymentsEntitlementNotInCatalogPublic.extend(internalFields).describe(
|
|
1844
|
+
PaymentsEntitlementNotInCatalogPublic.description ?? "",
|
|
1845
|
+
);
|
|
1846
|
+
const PaymentsSubjectUnresolved = PaymentsSubjectUnresolvedPublic.extend(internalFields).describe(
|
|
1847
|
+
PaymentsSubjectUnresolvedPublic.description ?? "",
|
|
1848
|
+
);
|
|
1849
|
+
const PaymentsSubscriptionChangeRefused = PaymentsSubscriptionChangeRefusedPublic.extend(internalFields).describe(
|
|
1850
|
+
PaymentsSubscriptionChangeRefusedPublic.description ?? "",
|
|
1851
|
+
);
|
|
1852
|
+
const ControlPlaneNotConnected = ControlPlaneNotConnectedPublic.extend(internalFields).describe(
|
|
1853
|
+
ControlPlaneNotConnectedPublic.description ?? "",
|
|
1854
|
+
);
|
|
1855
|
+
const ControlPlaneInvalidCredential = ControlPlaneInvalidCredentialPublic.extend(internalFields).describe(
|
|
1856
|
+
ControlPlaneInvalidCredentialPublic.description ?? "",
|
|
1857
|
+
);
|
|
1858
|
+
const ControlPlaneInsufficientScope = ControlPlaneInsufficientScopePublic.extend(internalFields).describe(
|
|
1859
|
+
ControlPlaneInsufficientScopePublic.description ?? "",
|
|
1860
|
+
);
|
|
1861
|
+
const ControlPlaneKeyNotFound = ControlPlaneKeyNotFoundPublic.extend(internalFields).describe(
|
|
1862
|
+
ControlPlaneKeyNotFoundPublic.description ?? "",
|
|
1863
|
+
);
|
|
1864
|
+
const ControlPlaneKeyConflict = ControlPlaneKeyConflictPublic.extend(internalFields).describe(
|
|
1865
|
+
ControlPlaneKeyConflictPublic.description ?? "",
|
|
1866
|
+
);
|
|
1867
|
+
const TestersCohortNotFound = TestersCohortNotFoundPublic.extend(internalFields).describe(
|
|
1868
|
+
TestersCohortNotFoundPublic.description ?? "",
|
|
1869
|
+
);
|
|
1870
|
+
const TestersMemberNotFound = TestersMemberNotFoundPublic.extend(internalFields).describe(
|
|
1871
|
+
TestersMemberNotFoundPublic.description ?? "",
|
|
1872
|
+
);
|
|
1873
|
+
const TestersInvalidToken = TestersInvalidTokenPublic.extend(internalFields).describe(
|
|
1874
|
+
TestersInvalidTokenPublic.description ?? "",
|
|
1875
|
+
);
|
|
1876
|
+
const TestersRosterFull = TestersRosterFullPublic.extend(internalFields).describe(
|
|
1877
|
+
TestersRosterFullPublic.description ?? "",
|
|
1878
|
+
);
|
|
1879
|
+
const TestersWithdrawn = TestersWithdrawnPublic.extend(internalFields).describe(
|
|
1880
|
+
TestersWithdrawnPublic.description ?? "",
|
|
1881
|
+
);
|
|
1882
|
+
|
|
1883
|
+
const TestersCohortClosed = TestersCohortClosedPublic.extend(internalFields).describe(
|
|
1884
|
+
TestersCohortClosedPublic.description ?? "",
|
|
1885
|
+
);
|
|
1886
|
+
|
|
1887
|
+
const TestersAlreadyOnRoster = TestersAlreadyOnRosterPublic.extend(internalFields).describe(
|
|
1888
|
+
TestersAlreadyOnRosterPublic.description ?? "",
|
|
1889
|
+
);
|
|
1890
|
+
const TestersNudgeCooldown = TestersNudgeCooldownPublic.extend(internalFields).describe(
|
|
1891
|
+
TestersNudgeCooldownPublic.description ?? "",
|
|
1892
|
+
);
|
|
1893
|
+
const TestersCopyNotAllowed = TestersCopyNotAllowedPublic.extend(internalFields).describe(
|
|
1894
|
+
TestersCopyNotAllowedPublic.description ?? "",
|
|
1895
|
+
);
|
|
1896
|
+
const TestersNotConfigured = TestersNotConfiguredPublic.extend(internalFields).describe(
|
|
1897
|
+
TestersNotConfiguredPublic.description ?? "",
|
|
1898
|
+
);
|
|
1899
|
+
|
|
1900
|
+
const SupportNotFound = SupportNotFoundPublic.extend(internalFields).describe(SupportNotFoundPublic.description ?? "");
|
|
1901
|
+
const SupportInvalidCategory = SupportInvalidCategoryPublic.extend(internalFields).describe(
|
|
1902
|
+
SupportInvalidCategoryPublic.description ?? "",
|
|
1903
|
+
);
|
|
1904
|
+
const SupportUnparseableMessage = SupportUnparseableMessagePublic.extend(internalFields).describe(
|
|
1905
|
+
SupportUnparseableMessagePublic.description ?? "",
|
|
1906
|
+
);
|
|
1907
|
+
const SupportRejected = SupportRejectedPublic.extend(internalFields).describe(SupportRejectedPublic.description ?? "");
|
|
1908
|
+
const SupportClassificationFailed = SupportClassificationFailedPublic.extend(internalFields).describe(
|
|
1909
|
+
SupportClassificationFailedPublic.description ?? "",
|
|
1910
|
+
);
|
|
1911
|
+
const SupportReplyFailed = SupportReplyFailedPublic.extend(internalFields).describe(
|
|
1912
|
+
SupportReplyFailedPublic.description ?? "",
|
|
1913
|
+
);
|
|
1914
|
+
|
|
1915
|
+
/**
|
|
1916
|
+
* Every error **the kit itself defines**, in full: the public fields plus the operator-only `action` and `detail`.
|
|
1917
|
+
* The closed taxonomy, and the only union anything may switch over exhaustively. `ErrorPayload`
|
|
1918
|
+
* below is this plus the adopter seam, and that is what a `PithyError` carries.
|
|
1919
|
+
*/
|
|
1920
|
+
export const KitErrorPayload = z
|
|
1921
|
+
.discriminatedUnion("code", [
|
|
1922
|
+
InvalidInput,
|
|
1923
|
+
InvalidToken,
|
|
1924
|
+
Forbidden,
|
|
1925
|
+
AuthProviderUnavailable,
|
|
1926
|
+
NotFound,
|
|
1927
|
+
Conflict,
|
|
1928
|
+
RateLimit,
|
|
1929
|
+
Internal,
|
|
1930
|
+
UpstreamFailed,
|
|
1931
|
+
UpstreamTimeout,
|
|
1932
|
+
InvalidWorkflowParams,
|
|
1933
|
+
MissingWorkflowBinding,
|
|
1934
|
+
UnknownWorkflow,
|
|
1935
|
+
WorkflowFailed,
|
|
1936
|
+
WebhookUnverified,
|
|
1937
|
+
CloudflareNotConfigured,
|
|
1938
|
+
CloudflareRequestFailed,
|
|
1939
|
+
CloudflareInvalidResponse,
|
|
1940
|
+
SecretNotFound,
|
|
1941
|
+
SecretAlreadyExists,
|
|
1942
|
+
SecretInvalidValue,
|
|
1943
|
+
SecretCryptoFailed,
|
|
1944
|
+
SecretRotationUnrecorded,
|
|
1945
|
+
SecretRotationUnsupported,
|
|
1946
|
+
EmailTemplateNotFound,
|
|
1947
|
+
EmailInvalidPayload,
|
|
1948
|
+
EmailInvalidToken,
|
|
1949
|
+
EmailSuppressed,
|
|
1950
|
+
EmailRateLimited,
|
|
1951
|
+
EmailSendFailed,
|
|
1952
|
+
TurnstileMissingToken,
|
|
1953
|
+
TurnstileFailed,
|
|
1954
|
+
TurnstileConfig,
|
|
1955
|
+
AuditInvalidEvent,
|
|
1956
|
+
AuditWriteFailed,
|
|
1957
|
+
MediaNotFound,
|
|
1958
|
+
MediaUnsupported,
|
|
1959
|
+
MediaStorageFailed,
|
|
1960
|
+
MediaEnrichmentFailed,
|
|
1961
|
+
LeaderboardBoardNotFound,
|
|
1962
|
+
LeaderboardEntryNotFound,
|
|
1963
|
+
LeaderboardScoreRejected,
|
|
1964
|
+
LeaderboardSubmitForbidden,
|
|
1965
|
+
LeaderboardBoardImmutable,
|
|
1966
|
+
LeaderboardInvalidSchedule,
|
|
1967
|
+
MultiplayerGameNotFound,
|
|
1968
|
+
MultiplayerSessionNotFound,
|
|
1969
|
+
MultiplayerNotAMember,
|
|
1970
|
+
MultiplayerSessionFull,
|
|
1971
|
+
MultiplayerInvalidTransition,
|
|
1972
|
+
MultiplayerInvalidMove,
|
|
1973
|
+
LedgerCurrencyNotFound,
|
|
1974
|
+
LedgerAccountNotFound,
|
|
1975
|
+
LedgerHoldNotFound,
|
|
1976
|
+
LedgerInsufficientFunds,
|
|
1977
|
+
LedgerHoldNotOpen,
|
|
1978
|
+
LedgerInvalidAmount,
|
|
1979
|
+
RatingUnknownAlgorithm,
|
|
1980
|
+
RatingUnsupportedPlayerCount,
|
|
1981
|
+
RatingInvalidParams,
|
|
1982
|
+
RatingGameNotFound,
|
|
1983
|
+
RatingPoolNotFound,
|
|
1984
|
+
RatingRecordForbidden,
|
|
1985
|
+
MatchmakingRoomNotFound,
|
|
1986
|
+
MatchmakingRoomFull,
|
|
1987
|
+
MatchmakingInvalidCode,
|
|
1988
|
+
MatchmakingInviteNotFound,
|
|
1989
|
+
MatchmakingInviteForbidden,
|
|
1990
|
+
MatchmakingUserNotFound,
|
|
1991
|
+
MatchmakingAlreadyFriends,
|
|
1992
|
+
MatchmakingFriendRequestNotFound,
|
|
1993
|
+
MatchmakingNotQueued,
|
|
1994
|
+
StorageNotFound,
|
|
1995
|
+
StorageForbidden,
|
|
1996
|
+
StorageQuotaExceeded,
|
|
1997
|
+
StorageUploadIncomplete,
|
|
1998
|
+
StorageMultipartFailed,
|
|
1999
|
+
StorageShareExpired,
|
|
2000
|
+
StorageShareRevoked,
|
|
2001
|
+
VectorMetadataIndexDrift,
|
|
2002
|
+
VectorDimensionMismatch,
|
|
2003
|
+
VectorTopKExceeded,
|
|
2004
|
+
VectorFilterTooLarge,
|
|
2005
|
+
VectorMetadataTooLarge,
|
|
2006
|
+
VectorIndexNotFound,
|
|
2007
|
+
VectorUnfilterableField,
|
|
2008
|
+
PaymentsInvalidReceipt,
|
|
2009
|
+
PaymentsVerificationFailed,
|
|
2010
|
+
PaymentsWebhookUnverified,
|
|
2011
|
+
PaymentsRailNotConfigured,
|
|
2012
|
+
PaymentsReconcileNotProvisioned,
|
|
2013
|
+
PaymentsProductNotFound,
|
|
2014
|
+
PaymentsEnvironmentMismatch,
|
|
2015
|
+
PaymentsReceiptAlreadyOwned,
|
|
2016
|
+
PaymentsProviderUnavailable,
|
|
2017
|
+
PaymentsEntitlementRequired,
|
|
2018
|
+
PaymentsClawbackFailed,
|
|
2019
|
+
PaymentsDiscountInvalid,
|
|
2020
|
+
PaymentsEntitlementNotInCatalog,
|
|
2021
|
+
PaymentsSubjectUnresolved,
|
|
2022
|
+
PaymentsSubscriptionChangeRefused,
|
|
2023
|
+
ControlPlaneNotConnected,
|
|
2024
|
+
ControlPlaneInvalidCredential,
|
|
2025
|
+
ControlPlaneInsufficientScope,
|
|
2026
|
+
ControlPlaneKeyNotFound,
|
|
2027
|
+
ControlPlaneKeyConflict,
|
|
2028
|
+
SupportNotFound,
|
|
2029
|
+
SupportInvalidCategory,
|
|
2030
|
+
SupportUnparseableMessage,
|
|
2031
|
+
SupportRejected,
|
|
2032
|
+
SupportClassificationFailed,
|
|
2033
|
+
SupportReplyFailed,
|
|
2034
|
+
TestersCohortNotFound,
|
|
2035
|
+
TestersMemberNotFound,
|
|
2036
|
+
TestersInvalidToken,
|
|
2037
|
+
TestersRosterFull,
|
|
2038
|
+
TestersAlreadyOnRoster,
|
|
2039
|
+
TestersCohortClosed,
|
|
2040
|
+
TestersWithdrawn,
|
|
2041
|
+
TestersNudgeCooldown,
|
|
2042
|
+
TestersCopyNotAllowed,
|
|
2043
|
+
TestersNotConfigured,
|
|
2044
|
+
])
|
|
2045
|
+
.describe("Every error the kit defines, with the operator's action and detail. The closed taxonomy.");
|
|
2046
|
+
export type KitErrorPayload = z.infer<typeof KitErrorPayload>;
|
|
2047
|
+
|
|
2048
|
+
/**
|
|
2049
|
+
* The closed set of codes the kit defines. Exhaustive handling — a `switch` with no `default`, a
|
|
2050
|
+
* lookup table keyed by code — belongs on this type, never on {@link ErrorCode}, which is open by
|
|
2051
|
+
* construction and would make such a switch a lie the moment an adopter registers a code.
|
|
2052
|
+
*/
|
|
2053
|
+
export type KitErrorCode = KitErrorPayload["code"];
|
|
2054
|
+
|
|
2055
|
+
/**
|
|
2056
|
+
* The HTTP status the kit pins to a code, or `undefined` for a code it does not define.
|
|
2057
|
+
*
|
|
2058
|
+
* **Every kit member pins `status` to one literal, so a code recovered on its own carries its status
|
|
2059
|
+
* with it** — that is what this reads. It exists for the one place a code arrives without the payload
|
|
2060
|
+
* it came from: a `PithyError` raised inside a durable Workflow step, whose only channel out is the
|
|
2061
|
+
* text the engine records (`../workflow/stepMessage`). Nothing but `code`, `message` and `action`
|
|
2062
|
+
* survives that boundary, and reporting the fault under `cloudflare/request_failed` 502 told an
|
|
2063
|
+
* operator to retry a request that was delivered and terminally answered (pithy-sh/pithy#365).
|
|
2064
|
+
*
|
|
2065
|
+
* **The status is recovered, never invented.** A code this does not know — an adopter's own, a
|
|
2066
|
+
* `d1/*` fault class, a typo — answers `undefined`, and the caller says so with a code of its own
|
|
2067
|
+
* rather than guessing a number. Derived from the union rather than listed for the same reason
|
|
2068
|
+
* {@link KIT_ERROR_DOMAINS} is: a second table is a second thing to forget.
|
|
2069
|
+
*/
|
|
2070
|
+
const KIT_ERROR_STATUSES: ReadonlyMap<string, number> = new Map(
|
|
2071
|
+
KitErrorPayload.options.map((member) => [member.shape.code.value, member.shape.status.value] as const),
|
|
2072
|
+
);
|
|
2073
|
+
|
|
2074
|
+
/** The status pinned to a kit `code`, or `undefined` when the kit does not define it. */
|
|
2075
|
+
export function kitErrorStatus(code: string): number | undefined {
|
|
2076
|
+
return KIT_ERROR_STATUSES.get(code);
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
// --- The adopter seam: codes the kit does not (and should not) define ---
|
|
2080
|
+
//
|
|
2081
|
+
// The taxonomy above is closed on purpose — a pinned `status` per `code`, a discriminator TypeScript
|
|
2082
|
+
// can narrow on, no way to typo a member into existence. But closed is not the same as complete. An
|
|
2083
|
+
// adopter's own domains (`connect/device_code_expired`, `keys/rotation_locked`) are unrepresentable
|
|
2084
|
+
// in it, and the choice we left them was to reuse a kit code that means something else or to abandon
|
|
2085
|
+
// `PithyError`. Both are worse than widening the union here.
|
|
2086
|
+
//
|
|
2087
|
+
// So `ErrorPayload` is the kit union **plus** one open member, and three properties keep the seam
|
|
2088
|
+
// from costing what the closed set bought:
|
|
2089
|
+
//
|
|
2090
|
+
// 1. **The operator's fields still never reach a client.** An adopter member carries `action` and
|
|
2091
|
+
// `detail` exactly like a kit member, and the public projection has neither — so
|
|
2092
|
+
// `PublicErrorPayload` strips them on parse and the HTTP codec drops them on encode, per code and
|
|
2093
|
+
// without exception. That is the whole security boundary, and it is not per-member policy.
|
|
2094
|
+
// 2. **The kit's domains are reserved, exactly like the `pithy_` table prefix.** The open member
|
|
2095
|
+
// refuses any code under a domain the closed union uses — not merely the codes it spells today.
|
|
2096
|
+
// Refusing only the exact set would leave `auth/forbidden` with the wrong status failing, but
|
|
2097
|
+
// `payments/verifcation_failed` passing: a capability's typo would stop being a hard failure and
|
|
2098
|
+
// start shipping as a valid adopter code with a status nobody pinned. Reserving the domain also
|
|
2099
|
+
// leaves the kit free to add codes under its own in a minor release without landing on top of
|
|
2100
|
+
// an adopter's. So an adopter picks their own domain, and both sides stay out of each other's.
|
|
2101
|
+
// 3. **Kit narrowing survives.** The open code is branded, so `payload.code === "core/not_found"`
|
|
2102
|
+
// still narrows to exactly that member. An unbranded `` `${string}/${string}` `` would sit in
|
|
2103
|
+
// every narrowed union and break `.issues` on the validation member for every consumer.
|
|
2104
|
+
|
|
2105
|
+
/** The `domain` half of a `domain/reason` code — everything before the first slash. */
|
|
2106
|
+
function domainOf(code: string): string {
|
|
2107
|
+
const slash = code.indexOf("/");
|
|
2108
|
+
return slash === -1 ? code : code.slice(0, slash);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
/**
|
|
2112
|
+
* Every `domain` the kit's own codes live under, at runtime. Derived from the union rather than
|
|
2113
|
+
* listed, so a new capability's domain is reserved the moment its first code lands — there is no
|
|
2114
|
+
* second list to forget. Read off `KitErrorPayload`, the union the open member is actually checked
|
|
2115
|
+
* against; the public projection is kept in step by a parity test rather than by being the source.
|
|
2116
|
+
*/
|
|
2117
|
+
const KIT_ERROR_DOMAINS: ReadonlySet<string> = new Set(
|
|
2118
|
+
KitErrorPayload.options.map((member) => domainOf(member.shape.code.value)),
|
|
2119
|
+
);
|
|
2120
|
+
|
|
2121
|
+
/** The `domain` half of every code the kit defines, as a type. The reserved set, at compile time. */
|
|
2122
|
+
export type KitErrorDomain = KitErrorCode extends `${infer Domain}/${string}` ? Domain : never;
|
|
2123
|
+
|
|
2124
|
+
/** One `domain` or `reason` segment: lower snake_case, opening on a letter. */
|
|
2125
|
+
const codeSegment = z.string().regex(/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/);
|
|
2126
|
+
|
|
2127
|
+
/**
|
|
2128
|
+
* The most a `domain/reason` code may run to. Every kit code is a literal, so its length was fixed
|
|
2129
|
+
* by construction; an adopter's is not, and this schema also runs on the **decode** side — on an
|
|
2130
|
+
* error body parsed back from a service we do not control. Unbounded, a hostile or merely broken
|
|
2131
|
+
* peer would choose how many bytes land in our log lines and audit rows. Sixty-four per segment is
|
|
2132
|
+
* already twice the longest code the kit spells.
|
|
2133
|
+
*/
|
|
2134
|
+
const MAX_ERROR_CODE_LENGTH = 129;
|
|
2135
|
+
|
|
2136
|
+
/**
|
|
2137
|
+
* An adopter's code. Same `domain/reason` grammar the kit's own codes follow (CLAUDE.md §Errors),
|
|
2138
|
+
* under a domain the kit does not use. Branded so the only way to hold one is to have parsed it —
|
|
2139
|
+
* which is what keeps a kit literal out of the open member and every `code ===` narrow honest.
|
|
2140
|
+
*/
|
|
2141
|
+
export const ExtendedErrorCode = z
|
|
2142
|
+
.templateLiteral([codeSegment, "/", codeSegment])
|
|
2143
|
+
// The segment schemas above compile to a regex, and a regex has no length opinion — so the bound
|
|
2144
|
+
// is a check on the whole code rather than a `.max()` on each half, which would be silently lost.
|
|
2145
|
+
.check(z.maxLength(MAX_ERROR_CODE_LENGTH))
|
|
2146
|
+
.refine((code) => !KIT_ERROR_DOMAINS.has(domainOf(code)), {
|
|
2147
|
+
error: "That domain is the kit's. Namespace your codes under a domain of your own.",
|
|
2148
|
+
})
|
|
2149
|
+
.describe("An adopter-defined `domain/reason` code, outside the set the kit defines.")
|
|
2150
|
+
.brand<"ExtendedErrorCode">();
|
|
2151
|
+
export type ExtendedErrorCode = z.output<typeof ExtendedErrorCode>;
|
|
2152
|
+
|
|
2153
|
+
const ExtendedPublic = z
|
|
2154
|
+
.object({
|
|
2155
|
+
code: ExtendedErrorCode,
|
|
2156
|
+
status: z
|
|
2157
|
+
.int()
|
|
2158
|
+
.min(400)
|
|
2159
|
+
.max(599)
|
|
2160
|
+
.describe(
|
|
2161
|
+
"The HTTP status this code maps to. A kit member pins one literal status; an adopter's member cannot, so the bound is the range — an error is a 4xx or a 5xx, and nothing else may ride this union to the wire.",
|
|
2162
|
+
),
|
|
2163
|
+
...publicFields,
|
|
2164
|
+
})
|
|
2165
|
+
.describe("An error an adopter defined under their own domain, in the wire shape (no action, no detail).");
|
|
2166
|
+
|
|
2167
|
+
/**
|
|
2168
|
+
* Every error that can reach a client, from the kit or from the adopter. `action` and `detail` are
|
|
2169
|
+
* absent from both branches, so parsing a payload here strips them whatever defined the code.
|
|
2170
|
+
*/
|
|
2171
|
+
export const PublicErrorPayload = z
|
|
2172
|
+
.union([KitPublicErrorPayload, ExtendedPublic])
|
|
2173
|
+
.describe("The public shape of every error Pithy emits — kit and adopter alike, safe for the wire.");
|
|
2174
|
+
export type PublicErrorPayload = z.infer<typeof PublicErrorPayload>;
|
|
2175
|
+
|
|
2176
|
+
/** An adopter's own error, in full: their public fields plus the same operator-only `action` and `detail`. */
|
|
2177
|
+
export const ExtendedErrorPayload = ExtendedPublic.extend(internalFields).describe(
|
|
2178
|
+
"An error an adopter defined under their own domain, with the operator's action and detail.",
|
|
2179
|
+
);
|
|
2180
|
+
export type ExtendedErrorPayload = z.infer<typeof ExtendedErrorPayload>;
|
|
2181
|
+
|
|
2182
|
+
/**
|
|
2183
|
+
* Every error Pithy can carry, in full: the public fields plus the operator-only `action` and `detail`. This is
|
|
2184
|
+
* what a `PithyError` carries in memory; the HTTP codec encodes it down to `PublicErrorPayload`.
|
|
2185
|
+
* The kit branch is tried first, so a kit code is always validated against its pinned status.
|
|
2186
|
+
*/
|
|
2187
|
+
export const ErrorPayload = z
|
|
2188
|
+
.union([KitErrorPayload, ExtendedErrorPayload])
|
|
2189
|
+
.describe(
|
|
2190
|
+
"Every error Pithy can emit, with the operator's action and detail. The in-memory shape a PithyError carries.",
|
|
2191
|
+
);
|
|
2192
|
+
export type ErrorPayload = z.infer<typeof ErrorPayload>;
|
|
2193
|
+
|
|
2194
|
+
/** Every machine-readable error code: the kit's closed set, plus whatever an adopter registers. */
|
|
2195
|
+
export type ErrorCode = ErrorPayload["code"];
|