@fleetless/contracts 1.0.0 → 1.0.3
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/CHANGELOG.md +97 -2
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +136 -0
- package/README.md +49 -13
- package/SECURITY.md +55 -0
- package/artifacts/openapi.json +11 -11
- package/artifacts/routes.json +12 -12
- package/artifacts/schema/create-app-oidc-provider-request.schema.json +1 -1
- package/dist/alerts.d.ts +23 -28
- package/dist/alerts.js +23 -29
- package/dist/app-users.d.ts +18 -19
- package/dist/app-users.js +18 -20
- package/dist/apps.d.ts +21 -25
- package/dist/apps.js +42 -52
- package/dist/assets.d.ts +70 -132
- package/dist/assets.js +130 -223
- package/dist/audit.d.ts +14 -15
- package/dist/audit.js +28 -55
- package/dist/client-auth.d.ts +9 -9
- package/dist/client-auth.js +8 -9
- package/dist/common.d.ts +29 -37
- package/dist/common.js +28 -37
- package/dist/config-issues.d.ts +23 -25
- package/dist/config-issues.js +17 -17
- package/dist/config.d.ts +37 -44
- package/dist/config.js +145 -187
- package/dist/errors.d.ts +4 -3
- package/dist/errors.js +83 -116
- package/dist/identity.d.ts +24 -27
- package/dist/identity.js +23 -27
- package/dist/index.d.ts +4 -4
- package/dist/index.js +14 -15
- package/dist/introspection.d.ts +7 -6
- package/dist/introspection.js +6 -6
- package/dist/jobs.d.ts +16 -16
- package/dist/jobs.js +24 -29
- package/dist/mcp.d.ts +14 -15
- package/dist/mcp.js +12 -14
- package/dist/oauth.d.ts +21 -27
- package/dist/oauth.js +33 -43
- package/dist/protocol.d.ts +51 -62
- package/dist/protocol.js +107 -139
- package/dist/realtime.d.ts +53 -68
- package/dist/realtime.js +78 -104
- package/dist/rest.d.ts +183 -244
- package/dist/rest.js +305 -399
- package/dist/routes.d.ts +4 -3
- package/dist/routes.js +33 -32
- package/package.json +12 -7
package/dist/errors.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
/**
|
|
3
|
-
* The one error shape of the REST and realtime APIs
|
|
4
|
+
* The one error shape of the REST and realtime APIs: a stable
|
|
4
5
|
* machine-readable code plus a human message; validation errors name the
|
|
5
6
|
* field and the violated rule in `details`.
|
|
6
7
|
*/
|
|
@@ -11,7 +12,7 @@ export declare const apiError: z.ZodObject<{
|
|
|
11
12
|
}, z.core.$strip>;
|
|
12
13
|
export type ApiError = z.infer<typeof apiError>;
|
|
13
14
|
/**
|
|
14
|
-
* One violated
|
|
15
|
+
* One violated parameter rule. `details` on the envelope stays `unknown` — codes
|
|
15
16
|
* are an open set, so their payloads cannot all be enumerated — but the
|
|
16
17
|
* payload of `parameter_invalid` **is** pinned here, because otherwise every
|
|
17
18
|
* consumer guesses: the cloud emits one shape, the SDK sniffs for two, the
|
|
@@ -44,7 +45,7 @@ export declare const parameterInvalidDetails: z.ZodObject<{
|
|
|
44
45
|
}, z.core.$strip>;
|
|
45
46
|
export type ParameterInvalidDetails = z.infer<typeof parameterInvalidDetails>;
|
|
46
47
|
/**
|
|
47
|
-
* The codes in use
|
|
48
|
+
* The codes in use today. The wire deliberately allows any string — this
|
|
48
49
|
* list is the shared vocabulary, not a closed set, so a new refusal never
|
|
49
50
|
* needs a contracts release before it can be reported honestly.
|
|
50
51
|
*/
|
package/dist/errors.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
/**
|
|
4
|
-
* The one error shape of the REST and realtime APIs
|
|
4
|
+
* The one error shape of the REST and realtime APIs: a stable
|
|
5
5
|
* machine-readable code plus a human message; validation errors name the
|
|
6
6
|
* field and the violated rule in `details`.
|
|
7
7
|
*/
|
|
@@ -11,7 +11,7 @@ export const apiError = z.object({
|
|
|
11
11
|
details: z.unknown().optional(),
|
|
12
12
|
});
|
|
13
13
|
/**
|
|
14
|
-
* One violated
|
|
14
|
+
* One violated parameter rule. `details` on the envelope stays `unknown` — codes
|
|
15
15
|
* are an open set, so their payloads cannot all be enumerated — but the
|
|
16
16
|
* payload of `parameter_invalid` **is** pinned here, because otherwise every
|
|
17
17
|
* consumer guesses: the cloud emits one shape, the SDK sniffs for two, the
|
|
@@ -39,12 +39,12 @@ export const parameterInvalidDetails = z.object({
|
|
|
39
39
|
violations: z.array(parameterViolation).min(1),
|
|
40
40
|
});
|
|
41
41
|
/**
|
|
42
|
-
* The codes in use
|
|
42
|
+
* The codes in use today. The wire deliberately allows any string — this
|
|
43
43
|
* list is the shared vocabulary, not a closed set, so a new refusal never
|
|
44
44
|
* needs a contracts release before it can be reported honestly.
|
|
45
45
|
*/
|
|
46
46
|
export const ERROR_CODES = [
|
|
47
|
-
//
|
|
47
|
+
// Core.
|
|
48
48
|
'not_found',
|
|
49
49
|
'validation_error',
|
|
50
50
|
'bad_request',
|
|
@@ -52,7 +52,7 @@ export const ERROR_CODES = [
|
|
|
52
52
|
'invalid_token',
|
|
53
53
|
'protocol_mismatch',
|
|
54
54
|
'invalid_frame',
|
|
55
|
-
//
|
|
55
|
+
// Configuration.
|
|
56
56
|
'duplicate_slug',
|
|
57
57
|
'reserved_slug',
|
|
58
58
|
/**
|
|
@@ -67,14 +67,14 @@ export const ERROR_CODES = [
|
|
|
67
67
|
'invalid_rate',
|
|
68
68
|
'invalid_range',
|
|
69
69
|
'config_conflict',
|
|
70
|
-
//
|
|
70
|
+
// Reading.
|
|
71
71
|
'no_data',
|
|
72
|
-
//
|
|
72
|
+
// Talking to the robot.
|
|
73
73
|
'robot_offline',
|
|
74
74
|
'bridge_timeout',
|
|
75
|
-
//
|
|
75
|
+
// Identity and rights. `forbidden` is deliberately the answer both
|
|
76
76
|
// for "your role does not grant this" and for "there is no such slug":
|
|
77
|
-
// roles are the only filter
|
|
77
|
+
// roles are the only filter, and a caller must not be able to map
|
|
78
78
|
// the configuration of an app they have no rights in.
|
|
79
79
|
'unauthorized',
|
|
80
80
|
'forbidden',
|
|
@@ -95,15 +95,11 @@ export const ERROR_CODES = [
|
|
|
95
95
|
* project's list: a documented refusal no caller can receive, which a reader
|
|
96
96
|
* would reasonably branch on. */
|
|
97
97
|
/**
|
|
98
|
-
* The address is already taken — **globally, across every org
|
|
99
|
-
* 2026-08-29).
|
|
98
|
+
* The address is already taken — **globally, across every org**.
|
|
100
99
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* exactly one account in exactly one org, and this code means *somebody,
|
|
105
|
-
* somewhere already has this address* — the pre-redesign meaning the code's
|
|
106
|
-
* name always implied. There is no per-org reading of it any more.
|
|
100
|
+
* A Fleetless user's email is globally unique: one address is exactly one
|
|
101
|
+
* account in exactly one org, and this code means *somebody, somewhere
|
|
102
|
+
* already has this address*. There is no per-org reading of it.
|
|
107
103
|
*
|
|
108
104
|
* It stays an answer to a *write* an authenticated caller made — signing up,
|
|
109
105
|
* inviting or creating — never to a login, which may not say whether an
|
|
@@ -116,53 +112,36 @@ export const ERROR_CODES = [
|
|
|
116
112
|
'email_taken',
|
|
117
113
|
'identifier_taken',
|
|
118
114
|
'weak_password',
|
|
119
|
-
/* `not_a_member`
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* nothing emits and whose noun no longer exists is the third failure mode in
|
|
125
|
-
* this project's list: a guard written against a state no producer reports.
|
|
126
|
-
* The refusals that do the work are `forbidden` (silent about existence) and
|
|
115
|
+
/* `not_a_member` is gone. It had no producer anywhere, and its vocabulary was
|
|
116
|
+
* a deleted model's — "member" of an app's pool, in a platform whose access is
|
|
117
|
+
* an assignment. A code that nothing emits, whose noun no longer exists, is a
|
|
118
|
+
* refusal a consumer must still branch on and can never receive. The refusals
|
|
119
|
+
* that do the work are `forbidden` (silent about existence) and
|
|
127
120
|
* `tier_required` (about the caller's own tier). */
|
|
128
121
|
/**
|
|
129
122
|
* The account itself is blocked — distinct from `forbidden` on purpose: it
|
|
130
123
|
* tells the account holder something about *their own* account, and reveals
|
|
131
124
|
* nothing about any other principal or about what exists.
|
|
132
125
|
*
|
|
133
|
-
* **It has
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* landed. `users` has no `status` column, nothing reinstates one, and
|
|
138
|
-
* removing a user's assignments is what withdraws access instead — so those
|
|
139
|
-
* five sites went with the old tables.
|
|
140
|
-
*
|
|
141
|
-
* What is left in the cloud is a *shape* with no input: `TokenRefusalReason`
|
|
142
|
-
* still admits `'blocked'` and `sendTokenRefusal` still has an arm for it
|
|
143
|
-
* (`auth.ts`), as does `ws/realtime.ts` — but no site anywhere constructs
|
|
144
|
-
* `reason: 'blocked'`, so neither arm is reachable. Verified by grep in
|
|
145
|
-
* FL-007, after `routes.ts` listed this code on the dual-auth guard and a
|
|
146
|
-
* review asked what produces it. Nothing does.
|
|
126
|
+
* **It has no producer today.** No user table carries a `status` column, and
|
|
127
|
+
* removing a user's assignments is what withdraws access instead. The cloud
|
|
128
|
+
* still has code paths shaped to carry this refusal, but nothing constructs
|
|
129
|
+
* one, so no caller can receive it.
|
|
147
130
|
*
|
|
148
131
|
* Kept, like `mcp_disabled` and for the same reason: the reserved shape is
|
|
149
132
|
* the point, and a code removed from the vocabulary is a code the next
|
|
150
133
|
* producer re-invents differently. But **do not list it as a refusal of any
|
|
151
134
|
* route** — that would document an answer no caller can receive.
|
|
152
|
-
*
|
|
153
|
-
* The tense discipline this comment was written under still stands: it now
|
|
154
|
-
* says the producer is gone because the producer is gone, not because a plan
|
|
155
|
-
* expects it to be.
|
|
156
135
|
*/
|
|
157
136
|
'account_blocked',
|
|
158
|
-
//
|
|
159
|
-
/** One job per action slug
|
|
137
|
+
// The command path.
|
|
138
|
+
/** One job per action slug; the refusal carries what is running. */
|
|
160
139
|
'busy',
|
|
161
|
-
/** A parameter failed its
|
|
140
|
+
/** A parameter failed its declared rule; details name the field and the rule. */
|
|
162
141
|
'parameter_invalid',
|
|
163
|
-
/** The bridge could not account for this job after a restart
|
|
142
|
+
/** The bridge could not account for this job after a restart. */
|
|
164
143
|
'job_lost',
|
|
165
|
-
/** Another user holds this publisher and has not been quiet long enough
|
|
144
|
+
/** Another user holds this publisher and has not been quiet long enough. */
|
|
166
145
|
'publisher_busy',
|
|
167
146
|
/** A well-formed realtime frame this server does not know — the socket stays open. */
|
|
168
147
|
'unknown_command',
|
|
@@ -173,8 +152,8 @@ export const ERROR_CODES = [
|
|
|
173
152
|
* it sends them looking for a configuration mistake that is not there.
|
|
174
153
|
*/
|
|
175
154
|
'not_subscribable',
|
|
176
|
-
//
|
|
177
|
-
/** The robot is connected but this camera is not publishing
|
|
155
|
+
// Cameras.
|
|
156
|
+
/** The robot is connected but this camera is not publishing. */
|
|
178
157
|
'camera_offline',
|
|
179
158
|
/**
|
|
180
159
|
* Nothing has been captured yet. An answer, not a failure: a camera
|
|
@@ -192,7 +171,7 @@ export const ERROR_CODES = [
|
|
|
192
171
|
* nothing.
|
|
193
172
|
*/
|
|
194
173
|
'wrong_kind',
|
|
195
|
-
//
|
|
174
|
+
// Retention and history.
|
|
196
175
|
/**
|
|
197
176
|
* The slug exists and is granted, but is configured live-only, so there is
|
|
198
177
|
* no history to return. An empty array would be indistinguishable from a
|
|
@@ -209,7 +188,7 @@ export const ERROR_CODES = [
|
|
|
209
188
|
*/
|
|
210
189
|
'not_aggregatable',
|
|
211
190
|
/**
|
|
212
|
-
* An org quota
|
|
191
|
+
* An org quota is exhausted. The message names **which** one —
|
|
213
192
|
* "quota exceeded" without saying which is a dead end for whoever has to
|
|
214
193
|
* act on it. Recording stops; live values keep flowing, because a storage
|
|
215
194
|
* limit is not a reason to take a robot away from its operator.
|
|
@@ -226,13 +205,13 @@ export const ERROR_CODES = [
|
|
|
226
205
|
'credential_in_use',
|
|
227
206
|
/**
|
|
228
207
|
* An action goal was never accepted — no server answered within the
|
|
229
|
-
* bridge's patience
|
|
208
|
+
* bridge's patience. Distinct from `failed`, which
|
|
230
209
|
* means the robot tried: nothing tried here. It exists so a slug whose ROS
|
|
231
210
|
* server is absent cannot stay wedged forever with the platform reporting
|
|
232
211
|
* a machine as busy doing something it never started.
|
|
233
212
|
*/
|
|
234
213
|
'goal_timeout',
|
|
235
|
-
//
|
|
214
|
+
// Deletion.
|
|
236
215
|
/**
|
|
237
216
|
* A robot cannot be deleted while a live session is open. Refusing beats
|
|
238
217
|
* deleting for the same reason `credential_in_use` does: the session
|
|
@@ -246,15 +225,15 @@ export const ERROR_CODES = [
|
|
|
246
225
|
* A deletion destroyed some of a robot and then failed. The robot still
|
|
247
226
|
* exists and is **not intact**; retrying the delete is the way out.
|
|
248
227
|
*
|
|
249
|
-
* It exists because the alternative
|
|
228
|
+
* It exists because the alternative is a generic `internal_error`, which
|
|
250
229
|
* says "nothing happened" — and a caller who reads that goes looking for a
|
|
251
|
-
* transient glitch.
|
|
252
|
-
*
|
|
253
|
-
* event. A failure that cannot be told apart from a no-op is how that
|
|
254
|
-
*
|
|
230
|
+
* transient glitch. What it can hide is configuration, drafts, types and
|
|
231
|
+
* hundreds of thousands of rows already gone, the robot still listed, and no
|
|
232
|
+
* audit event. A failure that cannot be told apart from a no-op is how that
|
|
233
|
+
* state stays invisible.
|
|
255
234
|
*/
|
|
256
235
|
'robot_deletion_partial',
|
|
257
|
-
//
|
|
236
|
+
// Addressing.
|
|
258
237
|
/**
|
|
259
238
|
* The bridge will not queue another job: its queue is full.
|
|
260
239
|
*
|
|
@@ -272,7 +251,7 @@ export const ERROR_CODES = [
|
|
|
272
251
|
*
|
|
273
252
|
* The numbers ride with it for the reason `publisher_busy` carries
|
|
274
253
|
* `retry_after_ms`: a refusal that names a state and no action leaves the
|
|
275
|
-
* caller to busy-loop
|
|
254
|
+
* caller to busy-loop.
|
|
276
255
|
*/
|
|
277
256
|
'job_queue_full',
|
|
278
257
|
/**
|
|
@@ -281,9 +260,9 @@ export const ERROR_CODES = [
|
|
|
281
260
|
* **Path and query only.** A malformed uuid in a *body* is caught by the
|
|
282
261
|
* body schema first and answers `validation_error` — the same mistake under
|
|
283
262
|
* two codes, split by where the id sat. Stated here rather than promised
|
|
284
|
-
* away: a consumer branching on `invalid_uuid` must not expect it for a
|
|
285
|
-
*
|
|
286
|
-
*
|
|
263
|
+
* away: a consumer branching on `invalid_uuid` must not expect it for a body
|
|
264
|
+
* field. Unifying the two would mean refusing before schema validation on
|
|
265
|
+
* every route that takes an id.
|
|
287
266
|
*
|
|
288
267
|
* Distinct from `not_found`, which was the answer for both and made a
|
|
289
268
|
* **typo indistinguishable from a deletion**. A developer whose client
|
|
@@ -292,7 +271,7 @@ export const ERROR_CODES = [
|
|
|
292
271
|
* refused before any lookup — so it leaks nothing that `not_found` did not.
|
|
293
272
|
*/
|
|
294
273
|
'invalid_uuid',
|
|
295
|
-
//
|
|
274
|
+
// Identity, and the limit that has to exist before it.
|
|
296
275
|
/**
|
|
297
276
|
* Too many attempts. The details carry `retry_after_ms`, for the reason
|
|
298
277
|
* `publisher_busy` carries it: a refusal that names a state and no action
|
|
@@ -308,7 +287,7 @@ export const ERROR_CODES = [
|
|
|
308
287
|
/**
|
|
309
288
|
* The caller's **tier** is insufficient — an org Member reaching for what
|
|
310
289
|
* only an Owner may do. Distinct from `forbidden`, which stays deliberately
|
|
311
|
-
* silent about existence
|
|
290
|
+
* silent about existence: this one says nothing about the target
|
|
312
291
|
* either, only about the caller's own role, which they can already read.
|
|
313
292
|
*
|
|
314
293
|
* Without it, "ask an owner to do this" and "you have the wrong id" are the
|
|
@@ -322,7 +301,7 @@ export const ERROR_CODES = [
|
|
|
322
301
|
* ask for a new link.
|
|
323
302
|
*/
|
|
324
303
|
'token_spent',
|
|
325
|
-
//
|
|
304
|
+
// The command path, continued.
|
|
326
305
|
/**
|
|
327
306
|
* A **service call** was dispatched and never returned. Distinct from
|
|
328
307
|
* `goal_timeout`, which means an action goal was never *accepted* — nothing
|
|
@@ -331,13 +310,13 @@ export const ERROR_CODES = [
|
|
|
331
310
|
* It exists because the bridge previously bounded a hung service with
|
|
332
311
|
* nothing at all: `_invoke_service` took no patience, so the caller got
|
|
333
312
|
* `bridge_timeout` from the cloud while the job stayed `running` forever on
|
|
334
|
-
* both sides and the slug
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
313
|
+
* both sides and the slug busy for good. rclpy's
|
|
314
|
+
* `Client.remove_pending_request` abandons the pending future cheaply, so
|
|
315
|
+
* unlike the action path this one can guarantee the callback never fires
|
|
316
|
+
* late.
|
|
338
317
|
*/
|
|
339
318
|
'service_timeout',
|
|
340
|
-
//
|
|
319
|
+
// The asset store.
|
|
341
320
|
/**
|
|
342
321
|
* A URDF references a mesh the store does not have. Distinct from
|
|
343
322
|
* `not_found` on the URDF itself: the URDF is present and readable, and the
|
|
@@ -355,7 +334,7 @@ export const ERROR_CODES = [
|
|
|
355
334
|
* cannot be read without the limit.
|
|
356
335
|
*/
|
|
357
336
|
'asset_too_large',
|
|
358
|
-
//
|
|
337
|
+
// The hosted authorization server.
|
|
359
338
|
//
|
|
360
339
|
// **This comment was wrong in its first form and a teammate followed it
|
|
361
340
|
// faithfully into a conformance bug.** It said these were "management-side
|
|
@@ -375,9 +354,8 @@ export const ERROR_CODES = [
|
|
|
375
354
|
// (`/mcp/oauth/register` today), and as an ordinary `apiError` code at the
|
|
376
355
|
// developer-facing management routes.
|
|
377
356
|
//
|
|
378
|
-
// Every code below has a producer
|
|
379
|
-
//
|
|
380
|
-
// cataloguing, and a teammate was right to refuse to add one.
|
|
357
|
+
// Every code below has a producer. An enum value with no producer is a
|
|
358
|
+
// refusal a caller can never receive and a consumer must still branch on.
|
|
381
359
|
/**
|
|
382
360
|
* The app has not opted in to dynamic client registration. A normal app has
|
|
383
361
|
* no reason to accept self-registering clients, so the flag is off by
|
|
@@ -398,7 +376,7 @@ export const ERROR_CODES = [
|
|
|
398
376
|
* fix it.
|
|
399
377
|
*/
|
|
400
378
|
'idp_unavailable',
|
|
401
|
-
//
|
|
379
|
+
// The MCP server, and a THIRD dialect on the same process.
|
|
402
380
|
//
|
|
403
381
|
// The correction above is about two dialects; there are now three, and the
|
|
404
382
|
// MCP endpoint speaks the one that is neither. **Inside the protocol** —
|
|
@@ -407,9 +385,7 @@ export const ERROR_CODES = [
|
|
|
407
385
|
// general-purpose implementation of somebody else's specification, and a
|
|
408
386
|
// body it cannot parse is indistinguishable from a broken server.
|
|
409
387
|
//
|
|
410
|
-
// **
|
|
411
|
-
// caught it in the same comment block whose opening sentence is about a
|
|
412
|
-
// previous comment here misleading somebody.** The five refusals that happen
|
|
388
|
+
// **The claim is narrower than it first reads.** The five refusals that happen
|
|
413
389
|
// *before* a bearer token is read — unknown app or MCP off (`404`), no or
|
|
414
390
|
// bad token (`401`), foreign `Origin` (`403`), `GET`/`DELETE` (`405`),
|
|
415
391
|
// malformed body (`400`) — are plain HTTP and answer `apiError`, exactly as
|
|
@@ -418,7 +394,7 @@ export const ERROR_CODES = [
|
|
|
418
394
|
// `WWW-Authenticate` **header**, which is correct and present, not the body.
|
|
419
395
|
//
|
|
420
396
|
// So the rule is about the JSON-RPC layer, and the transport layer below it
|
|
421
|
-
// is ordinary
|
|
397
|
+
// is ordinary HTTP. Stating it as "never `apiError` anywhere" was the
|
|
422
398
|
// kind of tidy sentence that is easier to remember than the truth — and
|
|
423
399
|
// this file has now produced two of those about itself.
|
|
424
400
|
//
|
|
@@ -437,8 +413,8 @@ export const ERROR_CODES = [
|
|
|
437
413
|
* flag; the central-MCP cut deleted the per-app `/mcp/<identifier>` endpoint
|
|
438
414
|
* that flag gated, and 2026-08-29 removed the field itself from `app`, so
|
|
439
415
|
* the code stood for a year with nothing able to produce it. The
|
|
440
|
-
*
|
|
441
|
-
*
|
|
416
|
+
* per-app endpoint is back, with the switch on `appAuthConfig` rather than on
|
|
417
|
+
* `app`, and this is its refusal again.
|
|
442
418
|
*
|
|
443
419
|
* The lesson that survives is about the year in between: an enum member with
|
|
444
420
|
* no producer is not harmless, because a reader arriving at it takes it for
|
|
@@ -461,11 +437,11 @@ export const ERROR_CODES = [
|
|
|
461
437
|
* **Deliberately one code for both**: to a developer holding the console,
|
|
462
438
|
* the role's datasheet (`mcpRobotDatasheet`) already lists every exposure
|
|
463
439
|
* the role does grant, so a second code would split an outcome nobody acts
|
|
464
|
-
* on differently. To anyone else the two must be indistinguishable anyway
|
|
465
|
-
*
|
|
440
|
+
* on differently. To anyone else the two must be indistinguishable anyway,
|
|
441
|
+
* because roles are the only filter.
|
|
466
442
|
*/
|
|
467
443
|
'tool_not_available',
|
|
468
|
-
//
|
|
444
|
+
// Capabilities.
|
|
469
445
|
/**
|
|
470
446
|
* An app-wide **capability** the caller's role does not grant — today
|
|
471
447
|
* `assets` (`GET /api/robots/:id/assets` and the URDF/by-id byte routes)
|
|
@@ -475,31 +451,26 @@ export const ERROR_CODES = [
|
|
|
475
451
|
* **Distinct from `forbidden`, and the distinction is the point.**
|
|
476
452
|
* `forbidden` is deliberately silent about existence, because roles are the
|
|
477
453
|
* only filter and a slug the caller cannot use must be indistinguishable
|
|
478
|
-
* from a slug that is not there
|
|
454
|
+
* from a slug that is not there. A capability is not a slug: it is a
|
|
479
455
|
* switch in the console that the developer owns, and the caller reaching
|
|
480
456
|
* this refusal has already been proven to reach the robot. Answering
|
|
481
457
|
* `forbidden` there tells a developer only that they may not — not which
|
|
482
458
|
* toggle to flip — and a promise the console makes is exactly what these
|
|
483
459
|
* capabilities have historically failed to keep.
|
|
484
460
|
*
|
|
485
|
-
* Both gates
|
|
486
|
-
*
|
|
487
|
-
* client branch on which route it called, so `assets` was moved here.
|
|
461
|
+
* Both capability gates answer with this code. One decision with two codes
|
|
462
|
+
* would make a client branch on which route it called.
|
|
488
463
|
*/
|
|
489
464
|
'capability_required',
|
|
490
|
-
//
|
|
465
|
+
// Org-central identity.
|
|
491
466
|
/**
|
|
492
467
|
* **An org must keep at least one Owner**, so the last one is neither
|
|
493
468
|
* deletable nor demotable. 409, on both `DELETE /api/org/users/:id` and
|
|
494
469
|
* `PATCH /api/org/users/:id/tier`.
|
|
495
470
|
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
* `string` and nothing ever compared the two lists. So a consumer switching
|
|
500
|
-
* exhaustively over `ERROR_CODES` could not handle a code the server
|
|
501
|
-
* actually sends. Registered as part of carrying the rule onto the new
|
|
502
|
-
* tiers, and named as the pre-existing gap it was rather than as a new code.
|
|
471
|
+
* A code the server sends must be in this list, or a consumer switching
|
|
472
|
+
* exhaustively over `ERROR_CODES` cannot handle it. Nothing compares the two
|
|
473
|
+
* automatically, because the cloud's error helper takes a bare string.
|
|
503
474
|
*
|
|
504
475
|
* Deliberately not `forbidden` or `tier_required`: an Owner reaching this
|
|
505
476
|
* has every permission the act needs. The refusal is about the org's
|
|
@@ -507,7 +478,7 @@ export const ERROR_CODES = [
|
|
|
507
478
|
* caller could infer from a silence about existence.
|
|
508
479
|
*/
|
|
509
480
|
'last_owner',
|
|
510
|
-
//
|
|
481
|
+
// OIDC federation.
|
|
511
482
|
/**
|
|
512
483
|
* **The target is in a state that refuses the operation** — not the caller's
|
|
513
484
|
* rights, not the target's existence, but *what the target currently is*.
|
|
@@ -549,7 +520,7 @@ export const ERROR_CODES = [
|
|
|
549
520
|
* `routes/console-oauth.ts` in the same release.
|
|
550
521
|
*/
|
|
551
522
|
'signup_closed',
|
|
552
|
-
//
|
|
523
|
+
// Emitted by the cloud, catalogued late.
|
|
553
524
|
//
|
|
554
525
|
// Every one of the five below has had a live producer for some time; what
|
|
555
526
|
// they never had was an entry here. **Each was confirmed by grepping the
|
|
@@ -571,7 +542,7 @@ export const ERROR_CODES = [
|
|
|
571
542
|
* `409` from the configuration routes: the draft parses as YAML but its root
|
|
572
543
|
* is not a mapping — a list, a scalar, or an empty document. Distinct from
|
|
573
544
|
* `validation_error`, which is about a field inside a document that *is* one.
|
|
574
|
-
* Produced by
|
|
545
|
+
* Produced by the configuration draft route.
|
|
575
546
|
*/
|
|
576
547
|
'draft_not_a_document',
|
|
577
548
|
/**
|
|
@@ -579,23 +550,22 @@ export const ERROR_CODES = [
|
|
|
579
550
|
* it has no mapping for, and the code the realtime socket sends for the same
|
|
580
551
|
* state. It says nothing about the request, deliberately: a caller cannot act
|
|
581
552
|
* on it beyond retrying, and the detail belongs in the server's log rather
|
|
582
|
-
* than in a body a stranger receives. Produced by
|
|
583
|
-
*
|
|
553
|
+
* than in a body a stranger receives. Produced by the server's own error
|
|
554
|
+
* handler and by the realtime socket.
|
|
584
555
|
*/
|
|
585
556
|
'internal_error',
|
|
586
557
|
/**
|
|
587
558
|
* `422` from `POST /api/robots/:id/jobs/:slug/cancel`: the job exists and the
|
|
588
559
|
* caller may address it, but it is in a state that has nothing left to
|
|
589
560
|
* cancel — already settled, or of a kind that does not support cancellation.
|
|
590
|
-
* Produced by
|
|
591
|
-
* `cloud/src/routes/commands.ts`.
|
|
561
|
+
* Produced by the command layer and mapped onto the job routes.
|
|
592
562
|
*/
|
|
593
563
|
'not_cancellable',
|
|
594
564
|
/**
|
|
595
565
|
* `415`. The request carried a body in a media type the route does not read.
|
|
596
566
|
* It is the cloud-wide answer from the content-type parser, not one route's:
|
|
597
567
|
* a caller reaching it never got as far as validation, which is why this is
|
|
598
|
-
* not a `validation_error`. Produced by
|
|
568
|
+
* not a `validation_error`. Produced by the server itself.
|
|
599
569
|
*/
|
|
600
570
|
'unsupported_media_type',
|
|
601
571
|
/**
|
|
@@ -605,16 +575,14 @@ export const ERROR_CODES = [
|
|
|
605
575
|
* hash recorded on the interaction row.
|
|
606
576
|
*
|
|
607
577
|
* **The impersonation interstitial it also named is deleted** with the rest
|
|
608
|
-
* of the app OAuth flow
|
|
609
|
-
*
|
|
610
|
-
* different screens — which is the reason worth carrying forward: the check
|
|
611
|
-
* belongs on every verb that *renders* the step, not only on the one that
|
|
578
|
+
* of the app OAuth flow. The rule that outlives it: this defence goes on
|
|
579
|
+
* every verb that *renders* the step, not only on the one that
|
|
612
580
|
* completes it.
|
|
613
581
|
*
|
|
614
582
|
* Deliberately not `invalid_token` or `unauthorized`: nothing about the
|
|
615
583
|
* caller's credential is being refused, and the remedy is specific and
|
|
616
|
-
* actionable — start the flow again in this browser. Produced by
|
|
617
|
-
*
|
|
584
|
+
* actionable — start the flow again in this browser. Produced by both
|
|
585
|
+
* hosted authorization flows.
|
|
618
586
|
*/
|
|
619
587
|
'wrong_browser',
|
|
620
588
|
/**
|
|
@@ -628,9 +596,8 @@ export const ERROR_CODES = [
|
|
|
628
596
|
* why nothing noticed. The envelope validates; only the *code* was absent
|
|
629
597
|
* from the one list a client can match against, so a caller branching on
|
|
630
598
|
* `ERROR_CODES` fell through to its unknown-error arm for the single most
|
|
631
|
-
* common refusal
|
|
632
|
-
*
|
|
633
|
-
* `cloud/src/routes/config.ts`.
|
|
599
|
+
* common refusal an editor produces — a documented absence on the codes list
|
|
600
|
+
* itself. Produced by the configuration draft route.
|
|
634
601
|
*/
|
|
635
602
|
'invalid_yaml',
|
|
636
603
|
/**
|
|
@@ -640,7 +607,7 @@ export const ERROR_CODES = [
|
|
|
640
607
|
*
|
|
641
608
|
* Two codes rather than one, because the two say different things to whoever
|
|
642
609
|
* typed the text: the first means "this is not YAML", the second means "this
|
|
643
|
-
* is YAML I cannot keep". Produced by
|
|
610
|
+
* is YAML I cannot keep". Produced by the configuration draft route.
|
|
644
611
|
*/
|
|
645
612
|
'unstorable_yaml',
|
|
646
613
|
// 2026-09-05 — app-user auth (two identity spaces, the JSON client API).
|