@fleetless/contracts 1.0.0 → 1.0.2
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 +68 -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 +1 -1
- package/artifacts/routes.json +1 -1
- package/dist/alerts.d.ts +19 -24
- package/dist/alerts.js +18 -24
- package/dist/app-users.d.ts +7 -6
- package/dist/app-users.js +6 -6
- package/dist/apps.d.ts +21 -25
- package/dist/apps.js +40 -51
- package/dist/assets.d.ts +70 -132
- package/dist/assets.js +130 -223
- package/dist/audit.d.ts +11 -11
- package/dist/audit.js +25 -51
- package/dist/client-auth.d.ts +4 -4
- package/dist/client-auth.js +3 -4
- package/dist/common.d.ts +27 -35
- package/dist/common.js +26 -35
- package/dist/config-issues.d.ts +4 -3
- package/dist/config-issues.js +7 -6
- package/dist/config.d.ts +31 -37
- package/dist/config.js +81 -110
- package/dist/errors.d.ts +4 -3
- package/dist/errors.js +61 -87
- package/dist/identity.d.ts +18 -21
- package/dist/identity.js +17 -21
- package/dist/index.d.ts +4 -4
- package/dist/index.js +12 -13
- package/dist/introspection.d.ts +7 -6
- package/dist/introspection.js +6 -6
- package/dist/jobs.d.ts +12 -12
- package/dist/jobs.js +20 -25
- package/dist/mcp.d.ts +11 -12
- package/dist/mcp.js +10 -12
- package/dist/oauth.d.ts +13 -18
- package/dist/oauth.js +13 -19
- package/dist/protocol.d.ts +51 -62
- package/dist/protocol.js +107 -139
- package/dist/realtime.d.ts +53 -68
- package/dist/realtime.js +77 -103
- package/dist/rest.d.ts +182 -243
- package/dist/rest.js +301 -395
- package/dist/routes.d.ts +4 -3
- package/dist/routes.js +3 -2
- package/package.json +12 -7
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
|
|
@@ -130,39 +126,25 @@ export const ERROR_CODES = [
|
|
|
130
126
|
* tells the account holder something about *their own* account, and reveals
|
|
131
127
|
* nothing about any other principal or about what exists.
|
|
132
128
|
*
|
|
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.
|
|
129
|
+
* **It has no producer today.** No user table carries a `status` column, and
|
|
130
|
+
* removing a user's assignments is what withdraws access instead. The cloud
|
|
131
|
+
* still has code paths shaped to carry this refusal, but nothing constructs
|
|
132
|
+
* one, so no caller can receive it.
|
|
147
133
|
*
|
|
148
134
|
* Kept, like `mcp_disabled` and for the same reason: the reserved shape is
|
|
149
135
|
* the point, and a code removed from the vocabulary is a code the next
|
|
150
136
|
* producer re-invents differently. But **do not list it as a refusal of any
|
|
151
137
|
* 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
138
|
*/
|
|
157
139
|
'account_blocked',
|
|
158
|
-
//
|
|
159
|
-
/** One job per action slug
|
|
140
|
+
// The command path.
|
|
141
|
+
/** One job per action slug; the refusal carries what is running. */
|
|
160
142
|
'busy',
|
|
161
|
-
/** A parameter failed its
|
|
143
|
+
/** A parameter failed its declared rule; details name the field and the rule. */
|
|
162
144
|
'parameter_invalid',
|
|
163
|
-
/** The bridge could not account for this job after a restart
|
|
145
|
+
/** The bridge could not account for this job after a restart. */
|
|
164
146
|
'job_lost',
|
|
165
|
-
/** Another user holds this publisher and has not been quiet long enough
|
|
147
|
+
/** Another user holds this publisher and has not been quiet long enough. */
|
|
166
148
|
'publisher_busy',
|
|
167
149
|
/** A well-formed realtime frame this server does not know — the socket stays open. */
|
|
168
150
|
'unknown_command',
|
|
@@ -173,8 +155,8 @@ export const ERROR_CODES = [
|
|
|
173
155
|
* it sends them looking for a configuration mistake that is not there.
|
|
174
156
|
*/
|
|
175
157
|
'not_subscribable',
|
|
176
|
-
//
|
|
177
|
-
/** The robot is connected but this camera is not publishing
|
|
158
|
+
// Cameras.
|
|
159
|
+
/** The robot is connected but this camera is not publishing. */
|
|
178
160
|
'camera_offline',
|
|
179
161
|
/**
|
|
180
162
|
* Nothing has been captured yet. An answer, not a failure: a camera
|
|
@@ -192,7 +174,7 @@ export const ERROR_CODES = [
|
|
|
192
174
|
* nothing.
|
|
193
175
|
*/
|
|
194
176
|
'wrong_kind',
|
|
195
|
-
//
|
|
177
|
+
// Retention and history.
|
|
196
178
|
/**
|
|
197
179
|
* The slug exists and is granted, but is configured live-only, so there is
|
|
198
180
|
* no history to return. An empty array would be indistinguishable from a
|
|
@@ -209,7 +191,7 @@ export const ERROR_CODES = [
|
|
|
209
191
|
*/
|
|
210
192
|
'not_aggregatable',
|
|
211
193
|
/**
|
|
212
|
-
* An org quota
|
|
194
|
+
* An org quota is exhausted. The message names **which** one —
|
|
213
195
|
* "quota exceeded" without saying which is a dead end for whoever has to
|
|
214
196
|
* act on it. Recording stops; live values keep flowing, because a storage
|
|
215
197
|
* limit is not a reason to take a robot away from its operator.
|
|
@@ -226,13 +208,13 @@ export const ERROR_CODES = [
|
|
|
226
208
|
'credential_in_use',
|
|
227
209
|
/**
|
|
228
210
|
* An action goal was never accepted — no server answered within the
|
|
229
|
-
* bridge's patience
|
|
211
|
+
* bridge's patience. Distinct from `failed`, which
|
|
230
212
|
* means the robot tried: nothing tried here. It exists so a slug whose ROS
|
|
231
213
|
* server is absent cannot stay wedged forever with the platform reporting
|
|
232
214
|
* a machine as busy doing something it never started.
|
|
233
215
|
*/
|
|
234
216
|
'goal_timeout',
|
|
235
|
-
//
|
|
217
|
+
// Deletion.
|
|
236
218
|
/**
|
|
237
219
|
* A robot cannot be deleted while a live session is open. Refusing beats
|
|
238
220
|
* deleting for the same reason `credential_in_use` does: the session
|
|
@@ -246,15 +228,15 @@ export const ERROR_CODES = [
|
|
|
246
228
|
* A deletion destroyed some of a robot and then failed. The robot still
|
|
247
229
|
* exists and is **not intact**; retrying the delete is the way out.
|
|
248
230
|
*
|
|
249
|
-
* It exists because the alternative
|
|
231
|
+
* It exists because the alternative is a generic `internal_error`, which
|
|
250
232
|
* 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
|
-
*
|
|
233
|
+
* transient glitch. What it can hide is configuration, drafts, types and
|
|
234
|
+
* hundreds of thousands of rows already gone, the robot still listed, and no
|
|
235
|
+
* audit event. A failure that cannot be told apart from a no-op is how that
|
|
236
|
+
* state stays invisible.
|
|
255
237
|
*/
|
|
256
238
|
'robot_deletion_partial',
|
|
257
|
-
//
|
|
239
|
+
// Addressing.
|
|
258
240
|
/**
|
|
259
241
|
* The bridge will not queue another job: its queue is full.
|
|
260
242
|
*
|
|
@@ -272,7 +254,7 @@ export const ERROR_CODES = [
|
|
|
272
254
|
*
|
|
273
255
|
* The numbers ride with it for the reason `publisher_busy` carries
|
|
274
256
|
* `retry_after_ms`: a refusal that names a state and no action leaves the
|
|
275
|
-
* caller to busy-loop
|
|
257
|
+
* caller to busy-loop.
|
|
276
258
|
*/
|
|
277
259
|
'job_queue_full',
|
|
278
260
|
/**
|
|
@@ -281,9 +263,9 @@ export const ERROR_CODES = [
|
|
|
281
263
|
* **Path and query only.** A malformed uuid in a *body* is caught by the
|
|
282
264
|
* body schema first and answers `validation_error` — the same mistake under
|
|
283
265
|
* 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
|
-
*
|
|
266
|
+
* away: a consumer branching on `invalid_uuid` must not expect it for a body
|
|
267
|
+
* field. Unifying the two would mean refusing before schema validation on
|
|
268
|
+
* every route that takes an id.
|
|
287
269
|
*
|
|
288
270
|
* Distinct from `not_found`, which was the answer for both and made a
|
|
289
271
|
* **typo indistinguishable from a deletion**. A developer whose client
|
|
@@ -292,7 +274,7 @@ export const ERROR_CODES = [
|
|
|
292
274
|
* refused before any lookup — so it leaks nothing that `not_found` did not.
|
|
293
275
|
*/
|
|
294
276
|
'invalid_uuid',
|
|
295
|
-
//
|
|
277
|
+
// Identity, and the limit that has to exist before it.
|
|
296
278
|
/**
|
|
297
279
|
* Too many attempts. The details carry `retry_after_ms`, for the reason
|
|
298
280
|
* `publisher_busy` carries it: a refusal that names a state and no action
|
|
@@ -308,7 +290,7 @@ export const ERROR_CODES = [
|
|
|
308
290
|
/**
|
|
309
291
|
* The caller's **tier** is insufficient — an org Member reaching for what
|
|
310
292
|
* only an Owner may do. Distinct from `forbidden`, which stays deliberately
|
|
311
|
-
* silent about existence
|
|
293
|
+
* silent about existence: this one says nothing about the target
|
|
312
294
|
* either, only about the caller's own role, which they can already read.
|
|
313
295
|
*
|
|
314
296
|
* Without it, "ask an owner to do this" and "you have the wrong id" are the
|
|
@@ -322,7 +304,7 @@ export const ERROR_CODES = [
|
|
|
322
304
|
* ask for a new link.
|
|
323
305
|
*/
|
|
324
306
|
'token_spent',
|
|
325
|
-
//
|
|
307
|
+
// The command path, continued.
|
|
326
308
|
/**
|
|
327
309
|
* A **service call** was dispatched and never returned. Distinct from
|
|
328
310
|
* `goal_timeout`, which means an action goal was never *accepted* — nothing
|
|
@@ -331,13 +313,13 @@ export const ERROR_CODES = [
|
|
|
331
313
|
* It exists because the bridge previously bounded a hung service with
|
|
332
314
|
* nothing at all: `_invoke_service` took no patience, so the caller got
|
|
333
315
|
* `bridge_timeout` from the cloud while the job stayed `running` forever on
|
|
334
|
-
* both sides and the slug
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
316
|
+
* both sides and the slug busy for good. rclpy's
|
|
317
|
+
* `Client.remove_pending_request` abandons the pending future cheaply, so
|
|
318
|
+
* unlike the action path this one can guarantee the callback never fires
|
|
319
|
+
* late.
|
|
338
320
|
*/
|
|
339
321
|
'service_timeout',
|
|
340
|
-
//
|
|
322
|
+
// The asset store.
|
|
341
323
|
/**
|
|
342
324
|
* A URDF references a mesh the store does not have. Distinct from
|
|
343
325
|
* `not_found` on the URDF itself: the URDF is present and readable, and the
|
|
@@ -355,7 +337,7 @@ export const ERROR_CODES = [
|
|
|
355
337
|
* cannot be read without the limit.
|
|
356
338
|
*/
|
|
357
339
|
'asset_too_large',
|
|
358
|
-
//
|
|
340
|
+
// The hosted authorization server.
|
|
359
341
|
//
|
|
360
342
|
// **This comment was wrong in its first form and a teammate followed it
|
|
361
343
|
// faithfully into a conformance bug.** It said these were "management-side
|
|
@@ -375,9 +357,8 @@ export const ERROR_CODES = [
|
|
|
375
357
|
// (`/mcp/oauth/register` today), and as an ordinary `apiError` code at the
|
|
376
358
|
// developer-facing management routes.
|
|
377
359
|
//
|
|
378
|
-
// Every code below has a producer
|
|
379
|
-
//
|
|
380
|
-
// cataloguing, and a teammate was right to refuse to add one.
|
|
360
|
+
// Every code below has a producer. An enum value with no producer is a
|
|
361
|
+
// refusal a caller can never receive and a consumer must still branch on.
|
|
381
362
|
/**
|
|
382
363
|
* The app has not opted in to dynamic client registration. A normal app has
|
|
383
364
|
* no reason to accept self-registering clients, so the flag is off by
|
|
@@ -398,7 +379,7 @@ export const ERROR_CODES = [
|
|
|
398
379
|
* fix it.
|
|
399
380
|
*/
|
|
400
381
|
'idp_unavailable',
|
|
401
|
-
//
|
|
382
|
+
// The MCP server, and a THIRD dialect on the same process.
|
|
402
383
|
//
|
|
403
384
|
// The correction above is about two dialects; there are now three, and the
|
|
404
385
|
// MCP endpoint speaks the one that is neither. **Inside the protocol** —
|
|
@@ -407,9 +388,7 @@ export const ERROR_CODES = [
|
|
|
407
388
|
// general-purpose implementation of somebody else's specification, and a
|
|
408
389
|
// body it cannot parse is indistinguishable from a broken server.
|
|
409
390
|
//
|
|
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
|
|
391
|
+
// **The claim is narrower than it first reads.** The five refusals that happen
|
|
413
392
|
// *before* a bearer token is read — unknown app or MCP off (`404`), no or
|
|
414
393
|
// bad token (`401`), foreign `Origin` (`403`), `GET`/`DELETE` (`405`),
|
|
415
394
|
// malformed body (`400`) — are plain HTTP and answer `apiError`, exactly as
|
|
@@ -461,11 +440,11 @@ export const ERROR_CODES = [
|
|
|
461
440
|
* **Deliberately one code for both**: to a developer holding the console,
|
|
462
441
|
* the role's datasheet (`mcpRobotDatasheet`) already lists every exposure
|
|
463
442
|
* 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
|
-
*
|
|
443
|
+
* on differently. To anyone else the two must be indistinguishable anyway,
|
|
444
|
+
* because roles are the only filter.
|
|
466
445
|
*/
|
|
467
446
|
'tool_not_available',
|
|
468
|
-
//
|
|
447
|
+
// Capabilities.
|
|
469
448
|
/**
|
|
470
449
|
* An app-wide **capability** the caller's role does not grant — today
|
|
471
450
|
* `assets` (`GET /api/robots/:id/assets` and the URDF/by-id byte routes)
|
|
@@ -475,16 +454,15 @@ export const ERROR_CODES = [
|
|
|
475
454
|
* **Distinct from `forbidden`, and the distinction is the point.**
|
|
476
455
|
* `forbidden` is deliberately silent about existence, because roles are the
|
|
477
456
|
* only filter and a slug the caller cannot use must be indistinguishable
|
|
478
|
-
* from a slug that is not there
|
|
457
|
+
* from a slug that is not there. A capability is not a slug: it is a
|
|
479
458
|
* switch in the console that the developer owns, and the caller reaching
|
|
480
459
|
* this refusal has already been proven to reach the robot. Answering
|
|
481
460
|
* `forbidden` there tells a developer only that they may not — not which
|
|
482
461
|
* toggle to flip — and a promise the console makes is exactly what these
|
|
483
462
|
* capabilities have historically failed to keep.
|
|
484
463
|
*
|
|
485
|
-
* Both gates
|
|
486
|
-
*
|
|
487
|
-
* client branch on which route it called, so `assets` was moved here.
|
|
464
|
+
* Both capability gates answer with this code. One decision with two codes
|
|
465
|
+
* would make a client branch on which route it called.
|
|
488
466
|
*/
|
|
489
467
|
'capability_required',
|
|
490
468
|
// 2026-08-29 — org-central identity (D1/D2).
|
|
@@ -493,13 +471,9 @@ export const ERROR_CODES = [
|
|
|
493
471
|
* deletable nor demotable. 409, on both `DELETE /api/org/users/:id` and
|
|
494
472
|
* `PATCH /api/org/users/:id/tier`.
|
|
495
473
|
*
|
|
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.
|
|
474
|
+
* A code the server sends must be in this list, or a consumer switching
|
|
475
|
+
* exhaustively over `ERROR_CODES` cannot handle it. Nothing compares the two
|
|
476
|
+
* automatically, because the cloud's error helper takes a bare string.
|
|
503
477
|
*
|
|
504
478
|
* Deliberately not `forbidden` or `tier_required`: an Owner reaching this
|
|
505
479
|
* has every permission the act needs. The refusal is about the org's
|
|
@@ -549,7 +523,7 @@ export const ERROR_CODES = [
|
|
|
549
523
|
* `routes/console-oauth.ts` in the same release.
|
|
550
524
|
*/
|
|
551
525
|
'signup_closed',
|
|
552
|
-
//
|
|
526
|
+
// Emitted by the cloud, catalogued late.
|
|
553
527
|
//
|
|
554
528
|
// Every one of the five below has had a live producer for some time; what
|
|
555
529
|
// they never had was an entry here. **Each was confirmed by grepping the
|
package/dist/identity.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
/**
|
|
3
4
|
* **Fleetless users: the org's team, and the only people who reach the
|
|
@@ -128,7 +129,7 @@ export declare const fleetlessUserListResponse: z.ZodObject<{
|
|
|
128
129
|
}, z.core.$strip>;
|
|
129
130
|
export type FleetlessUserListResponse = z.infer<typeof fleetlessUserListResponse>;
|
|
130
131
|
/**
|
|
131
|
-
* Access plus refresh
|
|
132
|
+
* Access plus refresh. The access token is short-lived; the
|
|
132
133
|
* refresh token rotates on every use, so a stolen one is detectable when the
|
|
133
134
|
* original is presented again.
|
|
134
135
|
*
|
|
@@ -146,8 +147,8 @@ export declare const refreshRequest: z.ZodObject<{
|
|
|
146
147
|
}, z.core.$strip>;
|
|
147
148
|
export type RefreshRequest = z.infer<typeof refreshRequest>;
|
|
148
149
|
/**
|
|
149
|
-
* Registering an org creates the org and its first owner in one step
|
|
150
|
-
*
|
|
150
|
+
* Registering an org creates the org and its first owner in one step: whoever
|
|
151
|
+
* registers the organisation is the owner.
|
|
151
152
|
*/
|
|
152
153
|
export declare const signUpRequest: z.ZodObject<{
|
|
153
154
|
org_name: z.ZodString;
|
|
@@ -222,7 +223,7 @@ export declare const developerLoginRequest: z.ZodObject<{
|
|
|
222
223
|
}, z.core.$strip>;
|
|
223
224
|
export type DeveloperLoginRequest = z.infer<typeof developerLoginRequest>;
|
|
224
225
|
/**
|
|
225
|
-
* What happened to the mail, in four words instead of one
|
|
226
|
+
* What happened to the mail, in four words instead of one.
|
|
226
227
|
*
|
|
227
228
|
* `mail_sent: boolean` could not tell **"we have no SMTP configured"** from
|
|
228
229
|
* **"we tried and the server refused"**, so the console had to pick a sentence
|
|
@@ -387,10 +388,10 @@ export declare const tierChangeRequest: z.ZodObject<{
|
|
|
387
388
|
export type TierChangeRequest = z.infer<typeof tierChangeRequest>;
|
|
388
389
|
/**
|
|
389
390
|
* What a `forbidden` refusal carries when the reason is the caller's **tier**
|
|
390
|
-
* rather than a missing grant
|
|
391
|
+
* rather than a missing grant.
|
|
391
392
|
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
393
|
+
* `forbidden` is deliberately silent about *existence*, and that stays true —
|
|
394
|
+
* this says nothing about what the target is. But "your role does not
|
|
394
395
|
* permit this" and "there is no such thing" are the same answer today, and a
|
|
395
396
|
* developer cannot tell *ask an owner* from *you have the wrong id*. Naming
|
|
396
397
|
* the required tier reveals only what the caller could read off the docs.
|
|
@@ -429,8 +430,8 @@ export type PasswordChangeRequest = z.infer<typeof passwordChangeRequest>;
|
|
|
429
430
|
*
|
|
430
431
|
* **The response never says whether the address exists.** It is unauthenticated
|
|
431
432
|
* and would otherwise be an account-enumeration oracle — the one place where
|
|
432
|
-
*
|
|
433
|
-
*
|
|
433
|
+
* revealing nothing about what exists is not a preference but the whole point.
|
|
434
|
+
* So this answers the same way for a known and an unknown address, in
|
|
434
435
|
* status, body **and timing**, and any consumer that renders "no such account"
|
|
435
436
|
* from it has reintroduced the oracle.
|
|
436
437
|
*
|
|
@@ -463,24 +464,20 @@ export type PasswordResetConfirm = z.infer<typeof passwordResetConfirm>;
|
|
|
463
464
|
* An IdP issuer URL — **an attacker-supplied string that decides where the
|
|
464
465
|
* *server* connects.**
|
|
465
466
|
*
|
|
466
|
-
* `redirectUri` in `oauth.ts`
|
|
467
|
-
* loopback allow-list
|
|
468
|
-
*
|
|
469
|
-
* the
|
|
470
|
-
*
|
|
471
|
-
* through the app's IdP route, then caught the outbound discovery fetch on a
|
|
472
|
-
* listener he stood up. **That is this project's own question — which rules
|
|
473
|
-
* have we already written down, and where else do they apply — answered badly,
|
|
474
|
-
* one field over.**
|
|
467
|
+
* `redirectUri` in `oauth.ts` carries a parsed scheme check and an explicit
|
|
468
|
+
* loopback allow-list because it decides where a *credential* goes. A bare
|
|
469
|
+
* `z.url()` here would accept `file:///etc/passwd`, a cloud metadata address or
|
|
470
|
+
* an internal database host, and the server would then fetch it during issuer
|
|
471
|
+
* discovery. The same rule applies one field over.
|
|
475
472
|
*
|
|
476
473
|
* **What this shape can decide, it now decides:** http(s) only (so no `file:`,
|
|
477
474
|
* `gopher:`, `data:`), no credentials in the URL, no fragment, no query. RFC
|
|
478
|
-
* 8414
|
|
475
|
+
* 8414 builds the discovery URL from the issuer's path, so a query string
|
|
479
476
|
* there is meaningless and a `@` is a redirect trick.
|
|
480
477
|
*
|
|
481
478
|
* **What it cannot decide, stated rather than implied:** it cannot tell
|
|
482
|
-
* `http://localhost:8081
|
|
483
|
-
*
|
|
479
|
+
* a development identity provider on `http://localhost:8081` from a database
|
|
480
|
+
* on `http://127.0.0.1:5432`. Both are loopback http. So **this is
|
|
484
481
|
* not the SSRF defence and must not be mistaken for one.** The defence belongs
|
|
485
482
|
* at the fetch, in the cloud: refuse loopback, link-local and private ranges
|
|
486
483
|
* unless something explicitly opts in for development, and it names DNS
|
package/dist/identity.js
CHANGED
|
@@ -126,7 +126,7 @@ export const fleetlessUserListResponse = z.object({
|
|
|
126
126
|
}),
|
|
127
127
|
});
|
|
128
128
|
/**
|
|
129
|
-
* Access plus refresh
|
|
129
|
+
* Access plus refresh. The access token is short-lived; the
|
|
130
130
|
* refresh token rotates on every use, so a stolen one is detectable when the
|
|
131
131
|
* original is presented again.
|
|
132
132
|
*
|
|
@@ -146,8 +146,8 @@ export const sessionTokens = z.object({
|
|
|
146
146
|
});
|
|
147
147
|
export const refreshRequest = z.object({ refresh_token: z.string().min(1) });
|
|
148
148
|
/**
|
|
149
|
-
* Registering an org creates the org and its first owner in one step
|
|
150
|
-
*
|
|
149
|
+
* Registering an org creates the org and its first owner in one step: whoever
|
|
150
|
+
* registers the organisation is the owner.
|
|
151
151
|
*/
|
|
152
152
|
export const signUpRequest = z.object({
|
|
153
153
|
org_name: z.string().min(1).max(120),
|
|
@@ -198,7 +198,7 @@ export const developerLoginRequest = z.object({
|
|
|
198
198
|
password: z.string().min(1),
|
|
199
199
|
});
|
|
200
200
|
/**
|
|
201
|
-
* What happened to the mail, in four words instead of one
|
|
201
|
+
* What happened to the mail, in four words instead of one.
|
|
202
202
|
*
|
|
203
203
|
* `mail_sent: boolean` could not tell **"we have no SMTP configured"** from
|
|
204
204
|
* **"we tried and the server refused"**, so the console had to pick a sentence
|
|
@@ -353,10 +353,10 @@ export const patchFleetlessUserRequest = z
|
|
|
353
353
|
export const tierChangeRequest = z.object({ tier: orgAdminTier }).strict();
|
|
354
354
|
/**
|
|
355
355
|
* What a `forbidden` refusal carries when the reason is the caller's **tier**
|
|
356
|
-
* rather than a missing grant
|
|
356
|
+
* rather than a missing grant.
|
|
357
357
|
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
358
|
+
* `forbidden` is deliberately silent about *existence*, and that stays true —
|
|
359
|
+
* this says nothing about what the target is. But "your role does not
|
|
360
360
|
* permit this" and "there is no such thing" are the same answer today, and a
|
|
361
361
|
* developer cannot tell *ask an owner* from *you have the wrong id*. Naming
|
|
362
362
|
* the required tier reveals only what the caller could read off the docs.
|
|
@@ -392,8 +392,8 @@ export const passwordChangeRequest = z.object({
|
|
|
392
392
|
*
|
|
393
393
|
* **The response never says whether the address exists.** It is unauthenticated
|
|
394
394
|
* and would otherwise be an account-enumeration oracle — the one place where
|
|
395
|
-
*
|
|
396
|
-
*
|
|
395
|
+
* revealing nothing about what exists is not a preference but the whole point.
|
|
396
|
+
* So this answers the same way for a known and an unknown address, in
|
|
397
397
|
* status, body **and timing**, and any consumer that renders "no such account"
|
|
398
398
|
* from it has reintroduced the oracle.
|
|
399
399
|
*
|
|
@@ -424,24 +424,20 @@ export const passwordResetConfirm = z.object({
|
|
|
424
424
|
* An IdP issuer URL — **an attacker-supplied string that decides where the
|
|
425
425
|
* *server* connects.**
|
|
426
426
|
*
|
|
427
|
-
* `redirectUri` in `oauth.ts`
|
|
428
|
-
* loopback allow-list
|
|
429
|
-
*
|
|
430
|
-
* the
|
|
431
|
-
*
|
|
432
|
-
* through the app's IdP route, then caught the outbound discovery fetch on a
|
|
433
|
-
* listener he stood up. **That is this project's own question — which rules
|
|
434
|
-
* have we already written down, and where else do they apply — answered badly,
|
|
435
|
-
* one field over.**
|
|
427
|
+
* `redirectUri` in `oauth.ts` carries a parsed scheme check and an explicit
|
|
428
|
+
* loopback allow-list because it decides where a *credential* goes. A bare
|
|
429
|
+
* `z.url()` here would accept `file:///etc/passwd`, a cloud metadata address or
|
|
430
|
+
* an internal database host, and the server would then fetch it during issuer
|
|
431
|
+
* discovery. The same rule applies one field over.
|
|
436
432
|
*
|
|
437
433
|
* **What this shape can decide, it now decides:** http(s) only (so no `file:`,
|
|
438
434
|
* `gopher:`, `data:`), no credentials in the URL, no fragment, no query. RFC
|
|
439
|
-
* 8414
|
|
435
|
+
* 8414 builds the discovery URL from the issuer's path, so a query string
|
|
440
436
|
* there is meaningless and a `@` is a redirect trick.
|
|
441
437
|
*
|
|
442
438
|
* **What it cannot decide, stated rather than implied:** it cannot tell
|
|
443
|
-
* `http://localhost:8081
|
|
444
|
-
*
|
|
439
|
+
* a development identity provider on `http://localhost:8081` from a database
|
|
440
|
+
* on `http://127.0.0.1:5432`. Both are loopback http. So **this is
|
|
445
441
|
* not the SSRF defence and must not be mistaken for one.** The defence belongs
|
|
446
442
|
* at the fetch, in the cloud: refuse loopback, link-local and private ranges
|
|
447
443
|
* unless something explicitly opts in for development, and it names DNS
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
export { SLUG_RULE, ROS_NAME_RULE, ROS_TYPE_NAME_RULE, FIELD_PATH_RULE, slug, rosName, rosTypeName, fieldPath, wireSeqCursor, wireTimestampMs, applyErrorKind, applyError, } from './common.js';
|
|
2
3
|
export type { ApplyErrorKind, ApplyError } from './common.js';
|
|
3
4
|
export { MCP_PROTOCOL_VERSION, MCP_ENDPOINT_PATH, mcpAppEndpointPath, MCP_APP_PATHS, MCP_TOOL_NAME_MAX, MCP_ASSET_LINK_PATH, MCP_ASSET_LINK_TTL_MS, mcpToolNamePattern, mcpToolKind, mcpExposure, mcpCapabilities, mcpRobotDatasheet, mcpRolePreviewResponse, } from './mcp.js';
|
|
@@ -12,10 +13,9 @@ export { FLEETLESS_FORMAT_VERSION, RESERVED_SLUGS, parameterType, parameterSpec,
|
|
|
12
13
|
export type { CameraSource } from './config.js';
|
|
13
14
|
export type { ParameterType, ParameterSpec, ActionConfig, ServiceConfig, PublisherConfig, CameraConfig, CameraCredentials, AlertCondition, DatapointAlert, DatapointNumeric, DatapointRetention, DatapointChart, DatapointConfig, RobotConfigDoc, ValidationIssue, ConfigState, } from './config.js';
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* `config-issues.ts`'s header for what that window cost.
|
|
16
|
+
* The one account of what is wrong with a configuration document, shared by
|
|
17
|
+
* every layer that reports on one. See `config-issues.ts`'s header for why a
|
|
18
|
+
* second copy of this vocabulary is a defect rather than a convenience.
|
|
19
19
|
*/
|
|
20
20
|
export { DOCUMENT_ROOT_PATH, EXPOSURE_SECTIONS, schemaIssues, formatPath, splitFormatPath, configSchemaHash, } from './config-issues.js';
|
|
21
21
|
export type { SchemaIssue, ExposureSection } from './config-issues.js';
|
package/dist/index.js
CHANGED
|
@@ -2,31 +2,30 @@
|
|
|
2
2
|
export { SLUG_RULE, ROS_NAME_RULE, ROS_TYPE_NAME_RULE, FIELD_PATH_RULE, slug, rosName, rosTypeName, fieldPath, wireSeqCursor, wireTimestampMs, applyErrorKind, applyError, } from './common.js';
|
|
3
3
|
export { MCP_PROTOCOL_VERSION, MCP_ENDPOINT_PATH, mcpAppEndpointPath, MCP_APP_PATHS, MCP_TOOL_NAME_MAX, MCP_ASSET_LINK_PATH, MCP_ASSET_LINK_TTL_MS, mcpToolNamePattern, mcpToolKind, mcpExposure, mcpCapabilities, mcpRobotDatasheet, mcpRolePreviewResponse, } from './mcp.js';
|
|
4
4
|
export { PROTOCOL_VERSION, bridgeHello, cloudHelloOk, cloudHelloError, cloudPing, bridgePong, datapointFrame, bridgeState, bridgePressure, PRESSURE_SLUG, cloudConfig, bridgeConfigApplied, cloudIntrospectRequest, bridgeIntrospect, cloudTypeRequest, bridgeTypeDefinitions, cloudInvoke, cloudCancel, cloudPublish, bridgeJobUpdate, bridgeJobLost, snapshotHeader, cloudCameraStart, cloudCameraStop, bridgeCameraState, SNAPSHOT_MAX_BYTES, CLOSE_ROBOT_DELETED,
|
|
5
|
-
//
|
|
5
|
+
// Assets.
|
|
6
6
|
bridgeAssetsAvailable, cloudAssetRequest, bridgeAssetProgress,
|
|
7
|
-
//
|
|
7
|
+
// Addressing.
|
|
8
8
|
activeJob, DEFAULT_PATIENCE_MS, MAX_PATIENCE_MS, MIN_PATIENCE_MS, } from './protocol.js';
|
|
9
9
|
export { jobState, job, jobEvent, busyDetails, publisherBusyDetails, jobQueueFullDetails } from './jobs.js';
|
|
10
10
|
export { JOB_RUN_PAGE_MAX, JOB_RUN_RETENTION_DAYS, jobActor, jobRunKind, jobRun, jobRunQuery, jobRunListResponse, jobRunSummaryQuery, jobRunSummary, } from './jobs.js';
|
|
11
11
|
export { FLEETLESS_FORMAT_VERSION, RESERVED_SLUGS, parameterType, parameterSpec, parameterMap, serviceDescription, parameterDescription, messageTemplate, messageRef, messageBody, messageMap, PLACEHOLDER_RE, placeholderNames, actionConfig, serviceConfig, publisherConfig, cameraConfig, alertCondition, datapointAlert, rateThrottleHz, datapointNumeric, datapointRetention, datapointChart, datapointConfig, robotConfigDoc, validationIssue, configState, snapshotIntervalSeconds,
|
|
12
|
-
//
|
|
12
|
+
// The defaults the format names, so nobody invents them twice.
|
|
13
13
|
ALERT_SEVERITY_DEFAULT, ALERT_ENABLED_DEFAULT, RETENTION_INTERVAL_SECONDS_DEFAULT, CHART_WINDOW_MINUTES_DEFAULT,
|
|
14
|
-
//
|
|
14
|
+
// Retention, history and quotas.
|
|
15
15
|
cameraSource, cameraCredentials, } from './config.js';
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `config-issues.ts`'s header for what that window cost.
|
|
17
|
+
* The one account of what is wrong with a configuration document, shared by
|
|
18
|
+
* every layer that reports on one. See `config-issues.ts`'s header for why a
|
|
19
|
+
* second copy of this vocabulary is a defect rather than a convenience.
|
|
21
20
|
*/
|
|
22
21
|
export { DOCUMENT_ROOT_PATH, EXPOSURE_SECTIONS, schemaIssues, formatPath, splitFormatPath, configSchemaHash, } from './config-issues.js';
|
|
23
22
|
export { rosGraphEntry, rosGraph, typeField, typeDefinition, parameterFieldsOf } from './introspection.js';
|
|
24
23
|
export { robot, patchRobotResponse, robotToken, createRobotRequest, createRobotResponse, exposureCounts, robotListItem, robotListResponse, datapointValue, robotDetailResponse, configDraftResponse, putConfigDraftRequest, publishConfigResponse, configVersionsResponse, configVersionResponse, introspectionResponse, typesResponse, fetchTypesRequest, fetchTypesResponse, datapointDescriptor, datapointListResponse, robotDetailsDoc, putRobotDetailsResponse, putRobotDetailsRequest, invokeRequest, invokeResponse,
|
|
25
|
-
//
|
|
24
|
+
// Addressing.
|
|
26
25
|
cancelRequest, releaseLiveQuery, serviceCallResponse, invokeOrServiceResponse, publishRequest, jobResponse, robotJobsResponse, rateLimitDetails, exposure, exposureListResponse, SNAPSHOT_HEADERS, ASSET_UPLOAD_HEADERS, cameraDescriptor, cameraListResponse, liveSessionResponse, snapshotMetaResponse,
|
|
27
|
-
//
|
|
26
|
+
// Retention, history, quotas.
|
|
28
27
|
historyQuery, historySamplesResponse, historyBucketsResponse, historyResponse, orgQuotas, orgQuotaUsage, orgQuotaUsageCounts, robotDeletionSummary, RESOURCE_HEALTH_STATES, resourceHealthState, resourceHealthListResponse, orgHealthQuery, robotDeleteQuery,
|
|
29
|
-
//
|
|
28
|
+
// Robot rename, slug rename.
|
|
30
29
|
patchRobotRequest, renameSlugRequest, renameSlugResponse, slugUsageResponse, } from './rest.js';
|
|
31
30
|
export { LATENCY_BUCKET_MS, BRIDGE_LATENCY_RETENTION_DAYS, MAX_LATENCY_BUCKETS_PER_RESPONSE, latencyBucket, robotLatencySeries, orgLatencyQuery, orgLatencyResponse, USAGE_WINDOW_MAX_DAYS, usageMetric, usageDay, orgUsageQuery, usageRow, orgUsageResponse, } from './rest.js';
|
|
32
31
|
export { clientAuth, authOk, authError, clientInvoke, clientCancel, clientPublish, commandResult, errorFrame, clientSubscribe, clientUnsubscribe, subscribeError, datapointEvent, resourceHealthEvent, resourceHealthCleared, liveSessionEndReason, liveSessionEvent, ORG_EVENT_SAMPLE_INTERVAL_MS, ORG_EVENT_ORG_CEILING_PER_SECOND, ORG_EVENT_BUFFER_SIZE, ORG_EVENT_BUFFER_IDLE_MS, ORG_EVENT_DETAIL_MAX_BYTES, orgEventKind, orgEventSeverity, orgEvent, orgEventSubscribe, orgEventUnsubscribe, orgEventReplay, orgEventDropped, } from './realtime.js';
|
|
@@ -35,9 +34,9 @@ export { password, org, patchOrgResponse, sessionTokens, refreshRequest, signUpR
|
|
|
35
34
|
waitlistRequest, developerLoginRequest,
|
|
36
35
|
// 2026-09-05 — the two identity spaces (app-user-auth, D1).
|
|
37
36
|
USER_DISPLAY_NAME_MAX, orgAdminTier, fleetlessUser, fleetlessUserListResponse, createTeamInviteRequest, teamInvite, pendingTeamInvite, pendingTeamInviteListResponse, acceptTeamInviteRequest, patchFleetlessUserRequest, tierChangeRequest,
|
|
38
|
-
//
|
|
37
|
+
// Identity.
|
|
39
38
|
mailStatus, tierRequiredDetails, passwordChangeRequest, passwordResetRequest, passwordResetConfirm, idpIssuer,
|
|
40
|
-
//
|
|
39
|
+
// auth/me, org and member patches.
|
|
41
40
|
authMeResponse, patchOrgRequest, patchAuthMeRequest, } from './identity.js';
|
|
42
41
|
export { appIdentifier, app, appListResponse, createAppRequest, updateAppRequest, serverKeyToken, serverKey, serverKeyListResponse, createServerKeyResponse, role, roleListResponse, rolePermissions, } from './apps.js';
|
|
43
42
|
export { clientLoginRequest, clientRefreshRequest, clientLogoutRequest, clientRegisterRequest, clientVerifyEmailRequest, clientResendVerificationRequest, clientPasswordResetRequest, clientPasswordResetConfirmRequest, clientAcceptInvitationRequest, CLIENT_OIDC_CALLBACK_PATH, clientProviderListQuery, clientProviderListResponse, clientOidcStartQuery, clientOidcCallbackQuery, clientOidcExchangeRequest, clientOidcErrorCode, clientMcpInteraction, clientMcpInteractionDecisionResponse, mcpConsentGrant, mcpConsentGrantListResponse, clientIdentity, } from './client-auth.js';
|