@fleetless/contracts 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -1
- package/artifacts/constants.json +30 -4
- package/artifacts/openapi.json +667 -98
- package/artifacts/routes.json +97 -6
- package/artifacts/schema/apply-error.schema.json +2 -1
- package/artifacts/schema/asset-list-response.schema.json +77 -12
- package/artifacts/schema/asset-sync-status.schema.json +35 -8
- package/artifacts/schema/asset.schema.json +2 -3
- package/artifacts/schema/assets-clear-response.schema.json +23 -0
- package/artifacts/schema/authorization-server-metadata.schema.json +1 -1
- package/artifacts/schema/bridge-asset-progress.schema.json +14 -8
- package/artifacts/schema/bridge-config-applied.schema.json +2 -1
- package/artifacts/schema/bridge-link-mode.schema.json +36 -0
- package/artifacts/schema/bridge-state.schema.json +6 -1
- package/artifacts/schema/client-robot-list-item.schema.json +6 -1
- package/artifacts/schema/client-robot-list-response.schema.json +6 -1
- package/artifacts/schema/cloud-config.schema.json +90 -5
- package/artifacts/schema/cloud-hello-ok.schema.json +45 -0
- package/artifacts/schema/cloud-ping.schema.json +27 -1
- package/artifacts/schema/config-draft-response.schema.json +90 -5
- package/artifacts/schema/config-state.schema.json +2 -1
- package/artifacts/schema/config-version-response.schema.json +90 -5
- package/artifacts/schema/datapoint-config.schema.json +5 -0
- package/artifacts/schema/datapoint-frame.schema.json +4 -0
- package/artifacts/schema/datapoint-list-response.schema.json +2 -2
- package/artifacts/schema/dynamic-client-registration-request.schema.json +1 -1
- package/artifacts/schema/dynamic-client-registration-response.schema.json +1 -1
- package/artifacts/schema/joint-state-put-request.schema.json +23 -0
- package/artifacts/schema/joint-state-put-response.schema.json +24 -0
- package/artifacts/schema/oauth-token-request.schema.json +79 -41
- package/artifacts/schema/oauth-token-response.schema.json +1 -1
- package/artifacts/schema/org-quota-usage-counts.schema.json +0 -5
- package/artifacts/schema/org-quota-usage.schema.json +1 -12
- package/artifacts/schema/org-quotas.schema.json +1 -7
- package/artifacts/schema/robot-config-doc.schema.json +90 -5
- package/artifacts/schema/robot-deletion-summary.schema.json +2 -1
- package/artifacts/schema/robot-detail-response.schema.json +63 -2
- package/artifacts/schema/robot-list-item.schema.json +15 -1
- package/artifacts/schema/robot-list-response.schema.json +15 -1
- package/artifacts/schema/robot-token-rotate-response.schema.json +15 -0
- package/artifacts/schema-outgoing/bridge-asset-progress.schema.json +14 -8
- package/artifacts/schema-outgoing/bridge-config-applied.schema.json +2 -1
- package/artifacts/schema-outgoing/bridge-link-mode.schema.json +37 -0
- package/artifacts/schema-outgoing/datapoint-frame.schema.json +4 -0
- package/dist/assets.d.ts +85 -50
- package/dist/assets.js +152 -62
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +1 -1
- package/dist/client-robots.d.ts +2 -0
- package/dist/common.d.ts +10 -0
- package/dist/common.js +16 -1
- package/dist/config.d.ts +69 -1
- package/dist/config.js +86 -6
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -8
- package/dist/index.d.ts +10 -10
- package/dist/index.js +5 -5
- package/dist/oauth.d.ts +34 -19
- package/dist/oauth.js +39 -24
- package/dist/protocol.d.ts +150 -71
- package/dist/protocol.js +144 -87
- package/dist/rest.d.ts +137 -35
- package/dist/rest.js +98 -66
- package/dist/routes.js +68 -19
- package/package.json +1 -1
- package/artifacts/schema/bridge-pressure.schema.json +0 -292
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"type": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"const": "link_mode"
|
|
8
|
+
},
|
|
9
|
+
"low_bandwidth": {
|
|
10
|
+
"type": "boolean",
|
|
11
|
+
"description": "Whether the mode is active after this transition."
|
|
12
|
+
},
|
|
13
|
+
"reason": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": [
|
|
16
|
+
"lag",
|
|
17
|
+
"dwell",
|
|
18
|
+
"forced",
|
|
19
|
+
"recovered"
|
|
20
|
+
],
|
|
21
|
+
"description": "`lag`: the cloud-measured lag crossed the threshold; `dwell`: the bridge-measured queue dwell did; `forced`: `mode: on` or `off`; `recovered`: both measures stayed at or below the exit threshold."
|
|
22
|
+
},
|
|
23
|
+
"at_ms": {
|
|
24
|
+
"type": "integer",
|
|
25
|
+
"minimum": 0,
|
|
26
|
+
"maximum": 9007199254740991,
|
|
27
|
+
"description": "Bridge time of the transition, epoch milliseconds."
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"required": [
|
|
31
|
+
"type",
|
|
32
|
+
"low_bandwidth",
|
|
33
|
+
"reason",
|
|
34
|
+
"at_ms"
|
|
35
|
+
],
|
|
36
|
+
"additionalProperties": false
|
|
37
|
+
}
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"type": "integer",
|
|
18
18
|
"minimum": 0,
|
|
19
19
|
"maximum": 9007199254740991
|
|
20
|
+
},
|
|
21
|
+
"backfill": {
|
|
22
|
+
"description": "true when the sample was captured while the bridge was disconnected and is being replayed after the reconnect. The cloud keeps such a sample out of its lag measure; absent means live.",
|
|
23
|
+
"type": "boolean"
|
|
20
24
|
}
|
|
21
25
|
},
|
|
22
26
|
"required": [
|
package/dist/assets.d.ts
CHANGED
|
@@ -26,24 +26,27 @@ import { z } from 'zod';
|
|
|
26
26
|
*
|
|
27
27
|
* ---
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
29
|
+
* **Three kinds, and every one of them is a thing a renderer does something
|
|
30
|
+
* with.**
|
|
31
|
+
*
|
|
32
|
+
* A `urdf` is the description, a `mesh` is geometry, a `texture` is an image
|
|
33
|
+
* referenced by the URDF's own `<material><texture>` **or** by a mesh file
|
|
34
|
+
* internally (a `.dae`'s `<init_from>`). A client that renders a robot must
|
|
35
|
+
* know, from the asset list alone and before fetching anything, which bytes
|
|
36
|
+
* it has to pre-fetch. Every load in the browser goes through the SDK with
|
|
37
|
+
* the bearer token — there is no lazy second fetch a renderer can make on its
|
|
38
|
+
* own account — so "what must be in memory before anything renders" is a
|
|
39
|
+
* question the list has to be able to answer, and a kind meaning *something
|
|
40
|
+
* else* answers it for nothing.
|
|
41
|
+
*
|
|
42
|
+
* There was a fourth, `other`, and **no producer ever sent it**: the bridge
|
|
43
|
+
* classifies what it uploads and has only these three to choose from. It was
|
|
44
|
+
* a slot for a file nobody had, which every consumer still had to branch on.
|
|
41
45
|
*/
|
|
42
46
|
export declare const assetKind: z.ZodEnum<{
|
|
43
47
|
urdf: "urdf";
|
|
44
48
|
mesh: "mesh";
|
|
45
49
|
texture: "texture";
|
|
46
|
-
other: "other";
|
|
47
50
|
}>;
|
|
48
51
|
export type AssetKind = z.infer<typeof assetKind>;
|
|
49
52
|
/**
|
|
@@ -79,7 +82,6 @@ export declare const asset: z.ZodObject<{
|
|
|
79
82
|
urdf: "urdf";
|
|
80
83
|
mesh: "mesh";
|
|
81
84
|
texture: "texture";
|
|
82
|
-
other: "other";
|
|
83
85
|
}>;
|
|
84
86
|
name: z.ZodString;
|
|
85
87
|
media_type: z.ZodString;
|
|
@@ -175,32 +177,41 @@ export type AssetSyncResponse = z.infer<typeof assetSyncResponse>;
|
|
|
175
177
|
* field is additive; adding an enum member is not.
|
|
176
178
|
*/
|
|
177
179
|
/**
|
|
178
|
-
* **
|
|
180
|
+
* **How much asset storage a robot has: one number, the same for every robot.**
|
|
179
181
|
*
|
|
180
|
-
* It
|
|
181
|
-
*
|
|
182
|
-
*
|
|
182
|
+
* It replaces two dials that answered neither question well — a per-file
|
|
183
|
+
* ceiling, which refused a single large mesh while saying nothing about the
|
|
184
|
+
* robot's total, and a per-organisation quota, which said nothing about any
|
|
185
|
+
* one robot. A developer syncing a robot asks *will this robot's description
|
|
186
|
+
* fit*, and only this number answers it.
|
|
183
187
|
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
188
|
+
* **Content addressing still stores a shared blob once, and each robot's
|
|
189
|
+
* counter still carries it.** Two robots referencing the same mesh cost one
|
|
190
|
+
* object and count against both stores, so a robot's number never depends on
|
|
191
|
+
* another robot's — which is the only way "used of 1 GB" means anything on a
|
|
192
|
+
* page about one robot.
|
|
187
193
|
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
|
|
192
|
-
|
|
194
|
+
* Stated here, and in `constants.json`, because the bridge cannot import this
|
|
195
|
+
* package and the cloud enforces the check: a limit the sender guesses and
|
|
196
|
+
* the receiver enforces is two numbers that agree until one of them moves.
|
|
197
|
+
*/
|
|
198
|
+
export declare const ROBOT_ASSET_STORE_BYTES = 1000000000;
|
|
199
|
+
/**
|
|
200
|
+
* What a full store tells the caller — the same discipline as `job_queue_full`
|
|
201
|
+
* and `publisher_busy`: a refusal that names a state and no number leaves the
|
|
202
|
+
* caller unable to decide anything.
|
|
193
203
|
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
204
|
+
* Three numbers, because two of them answer different questions. `store_bytes`
|
|
205
|
+
* and `used_bytes` say how much room there is; `size_bytes` says what did not
|
|
206
|
+
* fit. Without the pair a developer cannot tell whether to delete something or
|
|
207
|
+
* to shrink the mesh, and "the store is full" answers neither.
|
|
197
208
|
*/
|
|
198
|
-
export declare const
|
|
199
|
-
|
|
200
|
-
|
|
209
|
+
export declare const assetStoreRefusedDetails: z.ZodObject<{
|
|
210
|
+
store_bytes: z.ZodNumber;
|
|
211
|
+
used_bytes: z.ZodNumber;
|
|
201
212
|
size_bytes: z.ZodNumber;
|
|
202
213
|
}, z.core.$strip>;
|
|
203
|
-
export type
|
|
214
|
+
export type AssetStoreRefusedDetails = z.infer<typeof assetStoreRefusedDetails>;
|
|
204
215
|
/**
|
|
205
216
|
* Why one reference did not make it into the store.
|
|
206
217
|
*
|
|
@@ -214,10 +225,17 @@ export type AssetTooLargeDetails = z.infer<typeof assetTooLargeDetails>;
|
|
|
214
225
|
* reconciliation may treat as gone.
|
|
215
226
|
* - **`upload_failed`** — the bytes exist and the transfer did not succeed.
|
|
216
227
|
* **Transient.** The asset is still wanted; a later sync will carry it.
|
|
217
|
-
* - **`refused`** — never attempted
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
228
|
+
* - **`refused`** — never attempted. Either the robot's asset store had no
|
|
229
|
+
* room for it, in which case `details` carries the three numbers, or a
|
|
230
|
+
* producer-side ceiling was hit (a `.dae` carrying more internal references
|
|
231
|
+
* than one file or one sync will report), in which case it does not.
|
|
232
|
+
* **Transient in the same sense**: nothing is known to be missing, only
|
|
233
|
+
* unexamined.
|
|
234
|
+
*
|
|
235
|
+
* There was a fourth, `too_large`, for a file over a per-file ceiling. That
|
|
236
|
+
* ceiling is gone — a robot has one store and nothing is refused for its own
|
|
237
|
+
* size — so the kind had no producer left and one fewer thing to branch on is
|
|
238
|
+
* the whole of the gain.
|
|
221
239
|
*
|
|
222
240
|
* A consumer that cannot act on the distinction may still print `reference`
|
|
223
241
|
* alone and lose nothing it had before.
|
|
@@ -226,7 +244,6 @@ export declare const assetFailureKind: z.ZodEnum<{
|
|
|
226
244
|
unresolvable: "unresolvable";
|
|
227
245
|
upload_failed: "upload_failed";
|
|
228
246
|
refused: "refused";
|
|
229
|
-
too_large: "too_large";
|
|
230
247
|
}>;
|
|
231
248
|
export type AssetFailureKind = z.infer<typeof assetFailureKind>;
|
|
232
249
|
export declare const assetFailure: z.ZodObject<{
|
|
@@ -235,10 +252,10 @@ export declare const assetFailure: z.ZodObject<{
|
|
|
235
252
|
unresolvable: "unresolvable";
|
|
236
253
|
upload_failed: "upload_failed";
|
|
237
254
|
refused: "refused";
|
|
238
|
-
too_large: "too_large";
|
|
239
255
|
}>;
|
|
240
256
|
details: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
241
|
-
|
|
257
|
+
store_bytes: z.ZodNumber;
|
|
258
|
+
used_bytes: z.ZodNumber;
|
|
242
259
|
size_bytes: z.ZodNumber;
|
|
243
260
|
}, z.core.$strip>>>;
|
|
244
261
|
}, z.core.$strip>;
|
|
@@ -265,14 +282,16 @@ export declare const assetSyncStatus: z.ZodObject<{
|
|
|
265
282
|
unresolvable: "unresolvable";
|
|
266
283
|
upload_failed: "upload_failed";
|
|
267
284
|
refused: "refused";
|
|
268
|
-
too_large: "too_large";
|
|
269
285
|
}>;
|
|
270
286
|
details: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
271
|
-
|
|
287
|
+
store_bytes: z.ZodNumber;
|
|
288
|
+
used_bytes: z.ZodNumber;
|
|
272
289
|
size_bytes: z.ZodNumber;
|
|
273
290
|
}, z.core.$strip>>>;
|
|
274
291
|
}, z.core.$strip>>;
|
|
275
292
|
reason: z.ZodNullable<z.ZodString>;
|
|
293
|
+
stored: z.ZodNullable<z.ZodNumber>;
|
|
294
|
+
announced: z.ZodNumber;
|
|
276
295
|
started_at: z.ZodISODateTime;
|
|
277
296
|
updated_at: z.ZodISODateTime;
|
|
278
297
|
}, z.core.$strip>;
|
|
@@ -285,7 +304,6 @@ export declare const assetListResponse: z.ZodObject<{
|
|
|
285
304
|
urdf: "urdf";
|
|
286
305
|
mesh: "mesh";
|
|
287
306
|
texture: "texture";
|
|
288
|
-
other: "other";
|
|
289
307
|
}>;
|
|
290
308
|
name: z.ZodString;
|
|
291
309
|
media_type: z.ZodString;
|
|
@@ -309,14 +327,16 @@ export declare const assetListResponse: z.ZodObject<{
|
|
|
309
327
|
unresolvable: "unresolvable";
|
|
310
328
|
upload_failed: "upload_failed";
|
|
311
329
|
refused: "refused";
|
|
312
|
-
too_large: "too_large";
|
|
313
330
|
}>;
|
|
314
331
|
details: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
315
|
-
|
|
332
|
+
store_bytes: z.ZodNumber;
|
|
333
|
+
used_bytes: z.ZodNumber;
|
|
316
334
|
size_bytes: z.ZodNumber;
|
|
317
335
|
}, z.core.$strip>>>;
|
|
318
336
|
}, z.core.$strip>>;
|
|
319
337
|
reason: z.ZodNullable<z.ZodString>;
|
|
338
|
+
stored: z.ZodNullable<z.ZodNumber>;
|
|
339
|
+
announced: z.ZodNumber;
|
|
320
340
|
started_at: z.ZodISODateTime;
|
|
321
341
|
updated_at: z.ZodISODateTime;
|
|
322
342
|
}, z.core.$strip>>;
|
|
@@ -332,8 +352,28 @@ export declare const assetListResponse: z.ZodObject<{
|
|
|
332
352
|
}, z.core.$strip>>;
|
|
333
353
|
}, z.core.$strip>;
|
|
334
354
|
urdf_available: z.ZodNullable<z.ZodBoolean>;
|
|
355
|
+
store: z.ZodObject<{
|
|
356
|
+
bytes: z.ZodNumber;
|
|
357
|
+
used_bytes: z.ZodNumber;
|
|
358
|
+
}, z.core.$strip>;
|
|
359
|
+
joint_state_slug: z.ZodNullable<z.ZodString>;
|
|
335
360
|
}, z.core.$strip>;
|
|
336
361
|
export type AssetListResponse = z.infer<typeof assetListResponse>;
|
|
362
|
+
/**
|
|
363
|
+
* What `DELETE /api/robots/:id/assets` answers — the store's escape hatch.
|
|
364
|
+
*
|
|
365
|
+
* A full store is never a dead end: the URDF upload is exempt from the gate,
|
|
366
|
+
* reconcile after every sync already frees what the new URDF stopped
|
|
367
|
+
* referencing, and this route is the third leg — an Owner can empty the
|
|
368
|
+
* store outright and let the next sync refill it. `deleted` and
|
|
369
|
+
* `bytes_freed` are what changed, not the store's state afterward — that is
|
|
370
|
+
* `0` by construction and not worth a field of its own.
|
|
371
|
+
*/
|
|
372
|
+
export declare const assetsClearResponse: z.ZodObject<{
|
|
373
|
+
deleted: z.ZodNumber;
|
|
374
|
+
bytes_freed: z.ZodNumber;
|
|
375
|
+
}, z.core.$strip>;
|
|
376
|
+
export type AssetsClearResponse = z.infer<typeof assetsClearResponse>;
|
|
337
377
|
/**
|
|
338
378
|
* The query of `GET /api/robots/:id/assets/missing`, the placeholder a
|
|
339
379
|
* rewritten URDF points at for a mesh Fleetless does not hold.
|
|
@@ -348,11 +388,6 @@ export declare const missingAssetQuery: z.ZodObject<{
|
|
|
348
388
|
name: z.ZodOptional<z.ZodString>;
|
|
349
389
|
}, z.core.$strip>;
|
|
350
390
|
export type MissingAssetQuery = z.infer<typeof missingAssetQuery>;
|
|
351
|
-
/**
|
|
352
|
-
* What an `asset_too_large` refusal tells the caller — the same discipline as
|
|
353
|
-
* `publisher_busy` and `job_queue_full`: a refusal that names a state and no
|
|
354
|
-
* number leaves the caller unable to decide anything.
|
|
355
|
-
*/
|
|
356
391
|
/**
|
|
357
392
|
* What a `busy` refusal on an asset sync has to carry.
|
|
358
393
|
*
|
package/dist/assets.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { slug } from './common.js';
|
|
3
4
|
/**
|
|
4
5
|
* The asset store.
|
|
5
6
|
*
|
|
@@ -26,20 +27,24 @@ import { z } from 'zod';
|
|
|
26
27
|
*
|
|
27
28
|
* ---
|
|
28
29
|
*
|
|
29
|
-
*
|
|
30
|
+
* **Three kinds, and every one of them is a thing a renderer does something
|
|
31
|
+
* with.**
|
|
30
32
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
* A `urdf` is the description, a `mesh` is geometry, a `texture` is an image
|
|
34
|
+
* referenced by the URDF's own `<material><texture>` **or** by a mesh file
|
|
35
|
+
* internally (a `.dae`'s `<init_from>`). A client that renders a robot must
|
|
36
|
+
* know, from the asset list alone and before fetching anything, which bytes
|
|
37
|
+
* it has to pre-fetch. Every load in the browser goes through the SDK with
|
|
38
|
+
* the bearer token — there is no lazy second fetch a renderer can make on its
|
|
39
|
+
* own account — so "what must be in memory before anything renders" is a
|
|
40
|
+
* question the list has to be able to answer, and a kind meaning *something
|
|
41
|
+
* else* answers it for nothing.
|
|
37
42
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
43
|
+
* There was a fourth, `other`, and **no producer ever sent it**: the bridge
|
|
44
|
+
* classifies what it uploads and has only these three to choose from. It was
|
|
45
|
+
* a slot for a file nobody had, which every consumer still had to branch on.
|
|
41
46
|
*/
|
|
42
|
-
export const assetKind = z.enum(['urdf', 'mesh', 'texture'
|
|
47
|
+
export const assetKind = z.enum(['urdf', 'mesh', 'texture']);
|
|
43
48
|
/**
|
|
44
49
|
* The `name` a URDF asset carries.
|
|
45
50
|
*
|
|
@@ -70,7 +75,7 @@ export const asset = z.object({
|
|
|
70
75
|
id: z.uuid().meta({ description: 'The asset\'s id in the store.' }),
|
|
71
76
|
robot_id: z.uuid().meta({ description: 'The robot this asset belongs to.' }),
|
|
72
77
|
kind: assetKind.meta({
|
|
73
|
-
description: 'What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with
|
|
78
|
+
description: 'What the file is: the `urdf` itself, a `mesh` it references, or a `texture` a mesh or the URDF paints with. A renderer decides from this alone, before fetching anything, what to pre-fetch.',
|
|
74
79
|
}),
|
|
75
80
|
/**
|
|
76
81
|
* What the robot called it — for a mesh, the `package://` URI the URDF
|
|
@@ -227,33 +232,44 @@ export const assetSyncResponse = z.object({
|
|
|
227
232
|
* field is additive; adding an enum member is not.
|
|
228
233
|
*/
|
|
229
234
|
/**
|
|
230
|
-
* **
|
|
235
|
+
* **How much asset storage a robot has: one number, the same for every robot.**
|
|
231
236
|
*
|
|
232
|
-
* It
|
|
233
|
-
*
|
|
234
|
-
*
|
|
237
|
+
* It replaces two dials that answered neither question well — a per-file
|
|
238
|
+
* ceiling, which refused a single large mesh while saying nothing about the
|
|
239
|
+
* robot's total, and a per-organisation quota, which said nothing about any
|
|
240
|
+
* one robot. A developer syncing a robot asks *will this robot's description
|
|
241
|
+
* fit*, and only this number answers it.
|
|
235
242
|
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
243
|
+
* **Content addressing still stores a shared blob once, and each robot's
|
|
244
|
+
* counter still carries it.** Two robots referencing the same mesh cost one
|
|
245
|
+
* object and count against both stores, so a robot's number never depends on
|
|
246
|
+
* another robot's — which is the only way "used of 1 GB" means anything on a
|
|
247
|
+
* page about one robot.
|
|
239
248
|
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
|
|
244
|
-
|
|
249
|
+
* Stated here, and in `constants.json`, because the bridge cannot import this
|
|
250
|
+
* package and the cloud enforces the check: a limit the sender guesses and
|
|
251
|
+
* the receiver enforces is two numbers that agree until one of them moves.
|
|
252
|
+
*/
|
|
253
|
+
export const ROBOT_ASSET_STORE_BYTES = 1_000_000_000;
|
|
254
|
+
/**
|
|
255
|
+
* What a full store tells the caller — the same discipline as `job_queue_full`
|
|
256
|
+
* and `publisher_busy`: a refusal that names a state and no number leaves the
|
|
257
|
+
* caller unable to decide anything.
|
|
245
258
|
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
259
|
+
* Three numbers, because two of them answer different questions. `store_bytes`
|
|
260
|
+
* and `used_bytes` say how much room there is; `size_bytes` says what did not
|
|
261
|
+
* fit. Without the pair a developer cannot tell whether to delete something or
|
|
262
|
+
* to shrink the mesh, and "the store is full" answers neither.
|
|
249
263
|
*/
|
|
250
|
-
export const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
264
|
+
export const assetStoreRefusedDetails = z.object({
|
|
265
|
+
store_bytes: z.number().int().positive().meta({
|
|
266
|
+
description: 'The robot\'s store, in bytes.',
|
|
267
|
+
}),
|
|
268
|
+
used_bytes: z.number().int().nonnegative().meta({
|
|
269
|
+
description: 'Bytes the robot\'s assets occupy before this upload.',
|
|
254
270
|
}),
|
|
255
271
|
size_bytes: z.number().int().positive().meta({
|
|
256
|
-
description: '
|
|
272
|
+
description: 'The refused upload, in bytes.',
|
|
257
273
|
}),
|
|
258
274
|
});
|
|
259
275
|
/**
|
|
@@ -269,15 +285,22 @@ export const assetTooLargeDetails = z.object({
|
|
|
269
285
|
* reconciliation may treat as gone.
|
|
270
286
|
* - **`upload_failed`** — the bytes exist and the transfer did not succeed.
|
|
271
287
|
* **Transient.** The asset is still wanted; a later sync will carry it.
|
|
272
|
-
* - **`refused`** — never attempted
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
288
|
+
* - **`refused`** — never attempted. Either the robot's asset store had no
|
|
289
|
+
* room for it, in which case `details` carries the three numbers, or a
|
|
290
|
+
* producer-side ceiling was hit (a `.dae` carrying more internal references
|
|
291
|
+
* than one file or one sync will report), in which case it does not.
|
|
292
|
+
* **Transient in the same sense**: nothing is known to be missing, only
|
|
293
|
+
* unexamined.
|
|
294
|
+
*
|
|
295
|
+
* There was a fourth, `too_large`, for a file over a per-file ceiling. That
|
|
296
|
+
* ceiling is gone — a robot has one store and nothing is refused for its own
|
|
297
|
+
* size — so the kind had no producer left and one fewer thing to branch on is
|
|
298
|
+
* the whole of the gain.
|
|
276
299
|
*
|
|
277
300
|
* A consumer that cannot act on the distinction may still print `reference`
|
|
278
301
|
* alone and lose nothing it had before.
|
|
279
302
|
*/
|
|
280
|
-
export const assetFailureKind = z.enum(['unresolvable', 'upload_failed', 'refused'
|
|
303
|
+
export const assetFailureKind = z.enum(['unresolvable', 'upload_failed', 'refused']);
|
|
281
304
|
export const assetFailure = z.object({
|
|
282
305
|
/**
|
|
283
306
|
* What could not be provided, verbatim — the same string `asset.name` would
|
|
@@ -290,35 +313,30 @@ export const assetFailure = z.object({
|
|
|
290
313
|
description: 'What could not be provided, verbatim — the same string the asset would have been stored under, so a developer can match it against their own workspace by eye. For a failed URDF upload it is `robot_description`, which is **not** a mesh URI: a consumer must not assume every entry is one.',
|
|
291
314
|
}),
|
|
292
315
|
kind: assetFailureKind.meta({
|
|
293
|
-
description: 'Why it failed. `unresolvable` means the reference names nothing the producer can find or may read, and is **permanent** — the only kind reconciliation may treat as gone. `upload_failed` means the bytes exist and the transfer did not succeed, `refused` means it was never attempted because
|
|
316
|
+
description: 'Why it failed. `unresolvable` means the reference names nothing the producer can find or may read, and is **permanent** — the only kind reconciliation may treat as gone. `upload_failed` means the bytes exist and the transfer did not succeed, and `refused` means it was never attempted, either because the robot\'s asset store had no room — then `details` carries the three numbers — or because a producer-side ceiling was hit.',
|
|
294
317
|
}),
|
|
295
318
|
/**
|
|
296
|
-
* **The
|
|
319
|
+
* **The three numbers behind a full store.**
|
|
297
320
|
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
* under one kind puts two facts on one key, each overwriting the other.
|
|
321
|
+
* A reason without numbers is not one a caller can act on. *"Refused"* does
|
|
322
|
+
* not answer whether to delete an old sync or shrink the mesh;
|
|
323
|
+
* `store_bytes`, `used_bytes` and `size_bytes` do.
|
|
302
324
|
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* described: a field whose rule lives only in a comment is a request.
|
|
325
|
+
* **Present only on `refused`, and not on every `refused`.** The other half
|
|
326
|
+
* of that kind is the single collective entry a sync emits when it stops
|
|
327
|
+
* naming individual failures, and no store number describes it — requiring
|
|
328
|
+
* details there would mean inventing them. So the enforcement below is the
|
|
329
|
+
* half that can be enforced: details belong to `refused` and to nothing
|
|
330
|
+
* else. A forced `details: null` on every `unresolvable` buys nothing.
|
|
310
331
|
*/
|
|
311
|
-
details:
|
|
312
|
-
description: 'The
|
|
332
|
+
details: assetStoreRefusedDetails.nullish().meta({
|
|
333
|
+
description: 'The three numbers behind a `refused` entry the robot\'s store had no room for, and absent for every other kind — a forced `null` on every `unresolvable` entry buys nothing. A `refused` entry may also carry no details: the producer\'s own ceiling is the other half of that kind, and no store number describes it.',
|
|
313
334
|
}),
|
|
314
335
|
}).superRefine((f, ctx) => {
|
|
315
336
|
// Enforced here, not merely described above — a rule that lives only in a
|
|
316
337
|
// comment gets filled with something else.
|
|
317
|
-
if (f.kind
|
|
318
|
-
ctx.addIssue({ code: 'custom', path: ['details'], message: '
|
|
319
|
-
}
|
|
320
|
-
if (f.kind !== 'too_large' && f.details != null) {
|
|
321
|
-
ctx.addIssue({ code: 'custom', path: ['details'], message: 'size details belong to `too_large` only' });
|
|
338
|
+
if (f.kind !== 'refused' && f.details != null) {
|
|
339
|
+
ctx.addIssue({ code: 'custom', path: ['details'], message: 'store details belong to `refused` only' });
|
|
322
340
|
}
|
|
323
341
|
});
|
|
324
342
|
export const assetSyncState = z.enum(['running', 'succeeded', 'failed']);
|
|
@@ -364,6 +382,36 @@ export const assetSyncStatus = z.object({
|
|
|
364
382
|
reason: z.string().min(1).nullable().meta({
|
|
365
383
|
description: 'Why the sync ended as it did, when that is not a per-reference fact. `null` when `failed` already says everything there is to say.',
|
|
366
384
|
}),
|
|
385
|
+
/**
|
|
386
|
+
* **What the receiver counted, next to what the producer claimed.**
|
|
387
|
+
*
|
|
388
|
+
* `state` is the bridge's own terminal frame and nothing else. A dev stack
|
|
389
|
+
* with no object store answered `500` to every upload and the sync still
|
|
390
|
+
* read `succeeded` — the producer had genuinely sent every file, and no
|
|
391
|
+
* one had asked the store. These two numbers are the cloud's own count,
|
|
392
|
+
* taken after the terminal frame: how many of the announced files
|
|
393
|
+
* (`assets_available`'s URDF and mesh list) its store actually holds.
|
|
394
|
+
*
|
|
395
|
+
* They are a pair because neither alone answers anything. `stored` without
|
|
396
|
+
* `announced` cannot say whether four files is all of them or a tenth of
|
|
397
|
+
* them, and `announced` alone is what the producer said it had, which is
|
|
398
|
+
* the claim under examination.
|
|
399
|
+
*
|
|
400
|
+
* A partial store is still `succeeded`: some meshes were never going to
|
|
401
|
+
* resolve, and the per-reference `failed` entries say which. An empty one
|
|
402
|
+
* under a `succeeded` frame is `failed`, because no transport succeeds at
|
|
403
|
+
* nothing.
|
|
404
|
+
*
|
|
405
|
+
* **`stored` is `null` while the sync is still running.** The count is
|
|
406
|
+
* taken once, after the robot's terminal frame; a `0` before then would
|
|
407
|
+
* say the store is empty when nobody has looked.
|
|
408
|
+
*/
|
|
409
|
+
stored: z.number().int().nonnegative().nullable().meta({
|
|
410
|
+
description: 'How many of the announced files the cloud\'s store actually holds. Counted once, after the robot reports the sync done, and `null` until then — nobody has looked yet. Read it against `announced`: `state` is what the robot reported, this is what arrived.',
|
|
411
|
+
}),
|
|
412
|
+
announced: z.number().int().nonnegative().meta({
|
|
413
|
+
description: 'How many files the robot announced for this sync — the URDF, if it has one, plus every mesh URI its description references. `0` when the robot announced nothing, and also `0` until it has answered at all: read it beside `stored`, which stays `null` until the terminal frame.',
|
|
414
|
+
}),
|
|
367
415
|
started_at: z.iso.datetime().meta({
|
|
368
416
|
description: 'When the sync started, as an ISO 8601 timestamp.',
|
|
369
417
|
}),
|
|
@@ -408,6 +456,53 @@ export const assetListResponse = z.object({
|
|
|
408
456
|
urdf_available: z.boolean().nullable().meta({
|
|
409
457
|
description: 'What the connected bridge says it *could* transfer — deliberately separate from what has been transferred. `null` when no bridge is connected, distinct from `false`: "no robot is online to ask" and "the robot has no URDF" send a developer to different places. After a publisher is killed rather than shut down this can read `true` for some seconds, on the underlying DDS liveliness timeout rather than on any check made here.',
|
|
410
458
|
}),
|
|
459
|
+
/**
|
|
460
|
+
* **How full this robot's store is, on the list that already names what is
|
|
461
|
+
* in it.** A page showing assets is the page where "will the next sync fit"
|
|
462
|
+
* is asked, and a second round trip to some quota endpoint would answer it
|
|
463
|
+
* about the organisation instead — which is a different number about a
|
|
464
|
+
* different thing.
|
|
465
|
+
*/
|
|
466
|
+
store: z.object({
|
|
467
|
+
bytes: z.number().int().positive().meta({
|
|
468
|
+
description: 'The robot\'s asset store, `ROBOT_ASSET_STORE_BYTES`.',
|
|
469
|
+
}),
|
|
470
|
+
used_bytes: z.number().int().nonnegative().meta({
|
|
471
|
+
description: 'Bytes its assets occupy.',
|
|
472
|
+
}),
|
|
473
|
+
}).meta({ description: 'How full this robot\'s store is.' }),
|
|
474
|
+
/**
|
|
475
|
+
* The datapoint that moves the joints in a renderer, chosen by a developer
|
|
476
|
+
* and stored on the robot. It rides on this list because a client that has
|
|
477
|
+
* just fetched the URDF and the meshes needs exactly one more thing to
|
|
478
|
+
* animate them, and asking a second endpoint for one slug is a round trip
|
|
479
|
+
* that buys nothing.
|
|
480
|
+
*
|
|
481
|
+
* `null` is an ordinary answer: none was ever chosen, or a publish removed
|
|
482
|
+
* the datapoint it named and the cloud cleared the mapping rather than
|
|
483
|
+
* leave it pointing at something that no longer qualifies.
|
|
484
|
+
*/
|
|
485
|
+
joint_state_slug: slug.nullable().meta({
|
|
486
|
+
description: 'The whole-message `sensor_msgs/msg/JointState` datapoint that drives the console\'s URDF viewer; null when none is chosen or a publish removed it. Set through `PUT /api/robots/:id/urdf/joint-state`.',
|
|
487
|
+
}),
|
|
488
|
+
});
|
|
489
|
+
/**
|
|
490
|
+
* What `DELETE /api/robots/:id/assets` answers — the store's escape hatch.
|
|
491
|
+
*
|
|
492
|
+
* A full store is never a dead end: the URDF upload is exempt from the gate,
|
|
493
|
+
* reconcile after every sync already frees what the new URDF stopped
|
|
494
|
+
* referencing, and this route is the third leg — an Owner can empty the
|
|
495
|
+
* store outright and let the next sync refill it. `deleted` and
|
|
496
|
+
* `bytes_freed` are what changed, not the store's state afterward — that is
|
|
497
|
+
* `0` by construction and not worth a field of its own.
|
|
498
|
+
*/
|
|
499
|
+
export const assetsClearResponse = z.object({
|
|
500
|
+
deleted: z.number().int().nonnegative().meta({
|
|
501
|
+
description: 'How many assets — URDF, meshes and textures together — were removed.',
|
|
502
|
+
}),
|
|
503
|
+
bytes_freed: z.number().int().nonnegative().meta({
|
|
504
|
+
description: 'The bytes the robot\'s store got back.',
|
|
505
|
+
}),
|
|
411
506
|
});
|
|
412
507
|
/**
|
|
413
508
|
* The query of `GET /api/robots/:id/assets/missing`, the placeholder a
|
|
@@ -426,11 +521,6 @@ export const missingAssetQuery = z
|
|
|
426
521
|
}),
|
|
427
522
|
})
|
|
428
523
|
.meta({ description: 'The one optional parameter of the missing-asset placeholder; it names the reference in the refusal.' });
|
|
429
|
-
/**
|
|
430
|
-
* What an `asset_too_large` refusal tells the caller — the same discipline as
|
|
431
|
-
* `publisher_busy` and `job_queue_full`: a refusal that names a state and no
|
|
432
|
-
* number leaves the caller unable to decide anything.
|
|
433
|
-
*/
|
|
434
524
|
/**
|
|
435
525
|
* What a `busy` refusal on an asset sync has to carry.
|
|
436
526
|
*
|
package/dist/audit.d.ts
CHANGED
|
@@ -121,6 +121,6 @@ export declare const AUDIT_CSV_COLUMNS: readonly ["seq", "at", "actor_kind", "ac
|
|
|
121
121
|
* The audit log is kept for **90 days**.
|
|
122
122
|
*
|
|
123
123
|
* A constant here so no consumer derives it a second time — the same reasoning
|
|
124
|
-
* as `
|
|
124
|
+
* as `ROBOT_ASSET_STORE_BYTES`.
|
|
125
125
|
*/
|
|
126
126
|
export declare const AUDIT_RETENTION_DAYS = 90;
|
package/dist/audit.js
CHANGED
|
@@ -203,6 +203,6 @@ export const AUDIT_CSV_COLUMNS = ['seq', 'at', 'actor_kind', 'actor_id', 'action
|
|
|
203
203
|
* The audit log is kept for **90 days**.
|
|
204
204
|
*
|
|
205
205
|
* A constant here so no consumer derives it a second time — the same reasoning
|
|
206
|
-
* as `
|
|
206
|
+
* as `ROBOT_ASSET_STORE_BYTES`.
|
|
207
207
|
*/
|
|
208
208
|
export const AUDIT_RETENTION_DAYS = 90;
|