@fleetless/contracts 1.2.0 → 3.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 +39 -1
- package/artifacts/constants.json +30 -4
- package/artifacts/openapi.json +882 -80
- package/artifacts/routes.json +217 -7
- package/artifacts/schema/app-deletion-summary.schema.json +51 -0
- 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/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/joint-state-put-request.schema.json +23 -0
- package/artifacts/schema/joint-state-put-response.schema.json +24 -0
- 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/put-app-auth-mcp-request.schema.json +27 -0
- package/artifacts/schema/put-app-auth-registration-request.schema.json +36 -0
- package/artifacts/schema/put-app-auth-urls-request.schema.json +48 -0
- 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/app-users.d.ts +25 -7
- package/dist/app-users.js +24 -6
- package/dist/apps.d.ts +22 -0
- package/dist/apps.js +33 -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 +12 -12
- package/dist/index.js +6 -6
- package/dist/protocol.d.ts +150 -71
- package/dist/protocol.js +144 -87
- package/dist/rest.d.ts +139 -35
- package/dist/rest.js +100 -66
- package/dist/routes.js +129 -21
- package/package.json +1 -1
- package/artifacts/schema/bridge-pressure.schema.json +0 -292
- package/artifacts/schema/put-app-auth-config-request.schema.json +0 -93
package/artifacts/routes.json
CHANGED
|
@@ -487,6 +487,65 @@
|
|
|
487
487
|
"transport": "http",
|
|
488
488
|
"notes": "A `default_role_id` naming a role of another app is refused: it is the one cross-app authorization check this shape can carry. Changing the robot set closes every live subscription the app's users hold, since a grant may no longer name a reachable robot."
|
|
489
489
|
},
|
|
490
|
+
{
|
|
491
|
+
"method": "GET",
|
|
492
|
+
"path": "/api/apps/:id/deletion-preview",
|
|
493
|
+
"section": "apps",
|
|
494
|
+
"summary": "Reports what deleting the app would destroy, without destroying it.",
|
|
495
|
+
"audience": "developer",
|
|
496
|
+
"auth": "developer",
|
|
497
|
+
"rateLimited": false,
|
|
498
|
+
"ownerTier": false,
|
|
499
|
+
"status": 200,
|
|
500
|
+
"params": [
|
|
501
|
+
{
|
|
502
|
+
"name": "id",
|
|
503
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`."
|
|
504
|
+
}
|
|
505
|
+
],
|
|
506
|
+
"query": null,
|
|
507
|
+
"request": null,
|
|
508
|
+
"response": "app-deletion-summary",
|
|
509
|
+
"errors": [
|
|
510
|
+
"unauthorized",
|
|
511
|
+
"token_expired",
|
|
512
|
+
"token_revoked",
|
|
513
|
+
"invalid_uuid",
|
|
514
|
+
"not_found"
|
|
515
|
+
],
|
|
516
|
+
"transport": "http",
|
|
517
|
+
"notes": "The same shape the delete's own audit event carries, computed by the same function on purpose: the confirmation dialog and the eventual receipt agree by construction, and any difference between them is real drift rather than two estimates that quietly disagree. \n\n**No `force` parameter, unlike the robot pair this is modelled on.** A robot's open live session is a single nameable state whose interruption is its own hazard, which is why that route makes the caller pass `force` explicitly. An app has no equivalent state to force past, and inventing one would be a guess wearing a guard's clothes — this preview is the guard."
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
"method": "DELETE",
|
|
521
|
+
"path": "/api/apps/:id",
|
|
522
|
+
"section": "apps",
|
|
523
|
+
"summary": "Deletes an app and everything it produced.",
|
|
524
|
+
"audience": "developer",
|
|
525
|
+
"auth": "developer",
|
|
526
|
+
"rateLimited": false,
|
|
527
|
+
"ownerTier": true,
|
|
528
|
+
"status": 204,
|
|
529
|
+
"params": [
|
|
530
|
+
{
|
|
531
|
+
"name": "id",
|
|
532
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`."
|
|
533
|
+
}
|
|
534
|
+
],
|
|
535
|
+
"query": null,
|
|
536
|
+
"request": null,
|
|
537
|
+
"response": null,
|
|
538
|
+
"errors": [
|
|
539
|
+
"unauthorized",
|
|
540
|
+
"token_expired",
|
|
541
|
+
"token_revoked",
|
|
542
|
+
"tier_required",
|
|
543
|
+
"invalid_uuid",
|
|
544
|
+
"not_found"
|
|
545
|
+
],
|
|
546
|
+
"transport": "http",
|
|
547
|
+
"notes": "Owner tier, and the gate runs **after** the org-scoped lookup: a developer-tier admin therefore sees the same `404` a stranger would for an app outside their org, rather than a tier refusal that confirms the id exists. A full cascade — its users, roles, server keys, invitations, OIDC provider configuration and mail templates all go, recorded once as `app.deleted` carrying an `appDeletionSummary`. Its robots are untouched: they belong to the org, not to the app. \n\n**No `force` parameter** — see `GET /api/apps/:id/deletion-preview`."
|
|
548
|
+
},
|
|
490
549
|
{
|
|
491
550
|
"method": "POST",
|
|
492
551
|
"path": "/api/apps/:id/roles",
|
|
@@ -1360,13 +1419,73 @@
|
|
|
1360
1419
|
"not_found"
|
|
1361
1420
|
],
|
|
1362
1421
|
"transport": "http",
|
|
1363
|
-
"notes": "One row per app, created with the app and never absent — an app that has configured nothing reads back the defaults rather than a `404`. `oidc_callback_url` is in the answer and not in the request: it is minted by the cloud from its own public base URL, is the same for every app and every provider, and is the value a developer registers at their identity provider."
|
|
1422
|
+
"notes": "One row per app, created with the app and never absent — an app that has configured nothing reads back the defaults rather than a `404`. `oidc_callback_url` is in the answer and not in the request: it is minted by the cloud from its own public base URL, is the same for every app and every provider, and is the value a developer registers at their identity provider. It stays read-only on every slice write below for a second reason: a writable callback URL would let a caller point the return leg of an OIDC sign-in, which carries an authorization code, at a host they own. `updated_at` is read-only for a duller one: the server stamps it on every write, and a client-supplied value would be a lie about when the row last changed."
|
|
1364
1423
|
},
|
|
1365
1424
|
{
|
|
1366
1425
|
"method": "PUT",
|
|
1367
|
-
"path": "/api/apps/:id/auth-config",
|
|
1426
|
+
"path": "/api/apps/:id/auth-config/registration",
|
|
1427
|
+
"section": "apps",
|
|
1428
|
+
"summary": "Replaces who may self-register, and from where.",
|
|
1429
|
+
"audience": "developer",
|
|
1430
|
+
"auth": "developer",
|
|
1431
|
+
"rateLimited": false,
|
|
1432
|
+
"ownerTier": false,
|
|
1433
|
+
"status": 200,
|
|
1434
|
+
"params": [
|
|
1435
|
+
{
|
|
1436
|
+
"name": "id",
|
|
1437
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`."
|
|
1438
|
+
}
|
|
1439
|
+
],
|
|
1440
|
+
"query": null,
|
|
1441
|
+
"request": "put-app-auth-registration-request",
|
|
1442
|
+
"response": "app-auth-config",
|
|
1443
|
+
"errors": [
|
|
1444
|
+
"unauthorized",
|
|
1445
|
+
"token_expired",
|
|
1446
|
+
"token_revoked",
|
|
1447
|
+
"invalid_uuid",
|
|
1448
|
+
"validation_error",
|
|
1449
|
+
"not_found"
|
|
1450
|
+
],
|
|
1451
|
+
"transport": "http",
|
|
1452
|
+
"notes": "**A replace, not a merge, and `.strict()`**: `self_registration`, `allowed_domains` and `allowed_origins` all arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's — see `GET`'s notes for why. \n\n`400 validation_error` is where the two field rules land: an entry in `allowed_domains` must be lowercase, since a capitalised one can never match a lowercased address, and an entry in `allowed_origins` must be a bare scheme-host-port with no path, since a browser sends nothing longer in its `Origin` header. Each refuses at configuration time rather than failing silently later. \n\nThe merge is server-side against the stored row, so this write never disturbs the urls or mcp slice."
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
"method": "PUT",
|
|
1456
|
+
"path": "/api/apps/:id/auth-config/urls",
|
|
1457
|
+
"section": "apps",
|
|
1458
|
+
"summary": "Replaces the three pages Fleetless's mails point at.",
|
|
1459
|
+
"audience": "developer",
|
|
1460
|
+
"auth": "developer",
|
|
1461
|
+
"rateLimited": false,
|
|
1462
|
+
"ownerTier": false,
|
|
1463
|
+
"status": 200,
|
|
1464
|
+
"params": [
|
|
1465
|
+
{
|
|
1466
|
+
"name": "id",
|
|
1467
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`."
|
|
1468
|
+
}
|
|
1469
|
+
],
|
|
1470
|
+
"query": null,
|
|
1471
|
+
"request": "put-app-auth-urls-request",
|
|
1472
|
+
"response": "app-auth-config",
|
|
1473
|
+
"errors": [
|
|
1474
|
+
"unauthorized",
|
|
1475
|
+
"token_expired",
|
|
1476
|
+
"token_revoked",
|
|
1477
|
+
"invalid_uuid",
|
|
1478
|
+
"validation_error",
|
|
1479
|
+
"not_found"
|
|
1480
|
+
],
|
|
1481
|
+
"transport": "http",
|
|
1482
|
+
"notes": "**A replace, not a merge, and `.strict()`**: `invite_url`, `verify_url` and `reset_url` all arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's — see `GET`'s notes for why. \n\n`400 validation_error` is where the field rule lands: a URL template must be https (or `http` on `localhost`) and carry its placeholder exactly once — a second occurrence leaves one literal in a mailed link, refused here rather than failing silently once the mail is sent. \n\nThe merge is server-side against the stored row, so this write never disturbs the registration or mcp slice."
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
"method": "PUT",
|
|
1486
|
+
"path": "/api/apps/:id/auth-config/mcp",
|
|
1368
1487
|
"section": "apps",
|
|
1369
|
-
"summary": "Replaces the
|
|
1488
|
+
"summary": "Replaces the MCP switch and its login URL together.",
|
|
1370
1489
|
"audience": "developer",
|
|
1371
1490
|
"auth": "developer",
|
|
1372
1491
|
"rateLimited": false,
|
|
@@ -1379,7 +1498,7 @@
|
|
|
1379
1498
|
}
|
|
1380
1499
|
],
|
|
1381
1500
|
"query": null,
|
|
1382
|
-
"request": "put-app-auth-
|
|
1501
|
+
"request": "put-app-auth-mcp-request",
|
|
1383
1502
|
"response": "app-auth-config",
|
|
1384
1503
|
"errors": [
|
|
1385
1504
|
"unauthorized",
|
|
@@ -1390,7 +1509,7 @@
|
|
|
1390
1509
|
"not_found"
|
|
1391
1510
|
],
|
|
1392
1511
|
"transport": "http",
|
|
1393
|
-
"notes": "**A replace, not a merge, and `.strict()`**:
|
|
1512
|
+
"notes": "**A replace, not a merge, and `.strict()`**: `mcp_enabled` and `mcp_login_url` both arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's — see `GET`'s notes for why. \n\n`mcp_login_url` answers to the same rule as the `urls` slice's three templates — https (or `http` on `localhost`), its placeholder exactly once — refused as `400 validation_error` rather than left to fail mid-OAuth, in a client's browser where no console screen is watching. \n\nThe merge is server-side against the stored row, so this write never disturbs the registration or urls slice."
|
|
1394
1513
|
},
|
|
1395
1514
|
{
|
|
1396
1515
|
"method": "GET",
|
|
@@ -3052,6 +3171,66 @@
|
|
|
3052
3171
|
"transport": "http",
|
|
3053
3172
|
"notes": "`token` is the only moment the raw bridge token exists outside the caller's hands — the cloud stores a hash, so nothing can read it back and a caller who loses it rotates rather than recovers. Audited: this mints a credential that can speak for the org from anywhere, and the event carries no `details`, because the one interesting value here is the token. `max_robots` is checked before anything is created, which is only safe because robot deletion exists."
|
|
3054
3173
|
},
|
|
3174
|
+
{
|
|
3175
|
+
"method": "POST",
|
|
3176
|
+
"path": "/api/robots/:id/token/rotate",
|
|
3177
|
+
"section": "robots",
|
|
3178
|
+
"summary": "Mints a new bridge token for the robot and invalidates the old one.",
|
|
3179
|
+
"audience": "developer",
|
|
3180
|
+
"auth": "developer",
|
|
3181
|
+
"rateLimited": false,
|
|
3182
|
+
"ownerTier": true,
|
|
3183
|
+
"status": 201,
|
|
3184
|
+
"params": [
|
|
3185
|
+
{
|
|
3186
|
+
"name": "id",
|
|
3187
|
+
"description": "The robot's uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`."
|
|
3188
|
+
}
|
|
3189
|
+
],
|
|
3190
|
+
"query": null,
|
|
3191
|
+
"request": null,
|
|
3192
|
+
"response": "robot-token-rotate-response",
|
|
3193
|
+
"errors": [
|
|
3194
|
+
"unauthorized",
|
|
3195
|
+
"token_expired",
|
|
3196
|
+
"token_revoked",
|
|
3197
|
+
"tier_required",
|
|
3198
|
+
"invalid_uuid",
|
|
3199
|
+
"not_found"
|
|
3200
|
+
],
|
|
3201
|
+
"transport": "http",
|
|
3202
|
+
"notes": "Owner tier, behind the org-scoped lookup, so a developer-tier admin sees the `404` a stranger would for a robot outside their org rather than a tier refusal that confirms the id exists. `token` is the only moment the new secret exists outside the caller's hands — the cloud stores a hash — so a caller who loses it rotates again. Audited as `robot.token_rotated`, with no `details`: the one interesting value here is the token. \n\n**It stops the bridge that is connected right now.** The old secret is gone the instant the hash is replaced, so the cloud closes that socket with `CLOSE_TOKEN_ROTATED` rather than leaving a bridge speaking on a credential nothing would accept again. A bridge that does not know the code reconnects and is refused at hello as `invalid_token`, which is the honest answer and ends the same way. **The robot is offline until somebody puts the new token on it** — this is a deliberate interruption, not a background rekey, and a fleet cannot be rotated without a visit to each robot."
|
|
3203
|
+
},
|
|
3204
|
+
{
|
|
3205
|
+
"method": "PUT",
|
|
3206
|
+
"path": "/api/robots/:id/urdf/joint-state",
|
|
3207
|
+
"section": "robots",
|
|
3208
|
+
"summary": "Chooses the datapoint whose joint positions move the robot's URDF, or clears it.",
|
|
3209
|
+
"audience": "developer",
|
|
3210
|
+
"auth": "developer",
|
|
3211
|
+
"rateLimited": false,
|
|
3212
|
+
"ownerTier": false,
|
|
3213
|
+
"status": 200,
|
|
3214
|
+
"params": [
|
|
3215
|
+
{
|
|
3216
|
+
"name": "id",
|
|
3217
|
+
"description": "The robot's uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`."
|
|
3218
|
+
}
|
|
3219
|
+
],
|
|
3220
|
+
"query": null,
|
|
3221
|
+
"request": "joint-state-put-request",
|
|
3222
|
+
"response": "joint-state-put-response",
|
|
3223
|
+
"errors": [
|
|
3224
|
+
"unauthorized",
|
|
3225
|
+
"token_expired",
|
|
3226
|
+
"token_revoked",
|
|
3227
|
+
"invalid_uuid",
|
|
3228
|
+
"not_found",
|
|
3229
|
+
"validation_error"
|
|
3230
|
+
],
|
|
3231
|
+
"transport": "http",
|
|
3232
|
+
"notes": "**What qualifies**: a datapoint of the **published** configuration whose ROS type is `sensor_msgs/msg/JointState` and which carries no `field` — the whole message, because positions and names arrive together and a single extracted field is half of a pose. Anything else is a `validation_error` naming that rule rather than a stored mapping that renders a battery reading as a robot. `{ \"slug\": null }` clears it, which is why the field is required and nullable rather than optional. \n\n**The mapping cannot outlive what it points at.** Every successful publish re-checks it against the new document and clears it when it no longer qualifies, recording `robot.joint_state_cleared` with the version that did it; a slug rename rewrites it like every other reference the editor already rewrites; deleting the robot takes it along. Every write through this route — a slug or `null` — is on the record too, as `robot.joint_state_set` with the actor and the slug, so a clear a person made is never mistaken for one a publish made. The stored value reads back on `GET /api/robots/:id/assets` as `joint_state_slug`, so a renderer fetches the URDF, the meshes and the mapping from one place."
|
|
3233
|
+
},
|
|
3055
3234
|
{
|
|
3056
3235
|
"method": "GET",
|
|
3057
3236
|
"path": "/api/robots",
|
|
@@ -4467,6 +4646,38 @@
|
|
|
4467
4646
|
"transport": "http",
|
|
4468
4647
|
"notes": "Developer sessions only, like starting a sync: the guard admits three caller kinds and the handler answers `401 unauthorized` to the other two. A sync belonging to another robot reads exactly like one that never existed, which is why the robot is resolved first."
|
|
4469
4648
|
},
|
|
4649
|
+
{
|
|
4650
|
+
"method": "DELETE",
|
|
4651
|
+
"path": "/api/robots/:id/assets",
|
|
4652
|
+
"section": "assets",
|
|
4653
|
+
"summary": "Empties a robot's asset store: every URDF, mesh and texture, gone at once.",
|
|
4654
|
+
"audience": "client",
|
|
4655
|
+
"auth": "developer_or_client",
|
|
4656
|
+
"rateLimited": false,
|
|
4657
|
+
"ownerTier": true,
|
|
4658
|
+
"status": 200,
|
|
4659
|
+
"params": [
|
|
4660
|
+
{
|
|
4661
|
+
"name": "id",
|
|
4662
|
+
"description": "The robot's uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`."
|
|
4663
|
+
}
|
|
4664
|
+
],
|
|
4665
|
+
"query": null,
|
|
4666
|
+
"request": null,
|
|
4667
|
+
"response": "assets-clear-response",
|
|
4668
|
+
"errors": [
|
|
4669
|
+
"unauthorized",
|
|
4670
|
+
"token_expired",
|
|
4671
|
+
"token_revoked",
|
|
4672
|
+
"forbidden",
|
|
4673
|
+
"tier_required",
|
|
4674
|
+
"invalid_uuid",
|
|
4675
|
+
"not_found",
|
|
4676
|
+
"busy"
|
|
4677
|
+
],
|
|
4678
|
+
"transport": "http",
|
|
4679
|
+
"notes": "The store's escape hatch: a full store is never a dead end, and this is the blunt third of the three answers to it — the URDF upload is exempt from the gate, reconcile after a sync already frees what the new URDF stopped referencing, and this route lets an Owner clear the robot outright. Owner tier, unconditionally, like starting a sync. Removes every asset of the robot and resets its store to `0`; the next sync fills it again. It does not touch the bridge's availability report — `urdf_available` still answers from the connected robot, unrelated to what this cloud happens to have stored. A clear while a sync is running is `409 busy` naming that sync's details, the same refusal starting a second sync gets, because deleting under a running upload would leave the store counter wrong."
|
|
4680
|
+
},
|
|
4470
4681
|
{
|
|
4471
4682
|
"method": "GET",
|
|
4472
4683
|
"path": "/api/org/quotas",
|
|
@@ -4622,14 +4833,13 @@
|
|
|
4622
4833
|
"errors": [
|
|
4623
4834
|
"unauthorized",
|
|
4624
4835
|
"rate_limited",
|
|
4625
|
-
"asset_too_large",
|
|
4626
4836
|
"validation_error",
|
|
4627
4837
|
"not_found",
|
|
4628
4838
|
"quota_exceeded",
|
|
4629
4839
|
"bad_request"
|
|
4630
4840
|
],
|
|
4631
4841
|
"transport": "http",
|
|
4632
|
-
"notes": "The body is the **raw file bytes**, not JSON, so it has no request schema; everything about the file — its kind, its name, its sync id and its announced size — rides in the `x-fleetless-asset-*` headers `ASSET_UPLOAD_HEADERS` names. The credential is a short-lived upload token minted by `POST /api/robots/:id/assets/sync`, verified in a `preParsing` hook so a refusal precedes the work rather than following it: a `preHandler` would already have buffered the whole file.
|
|
4842
|
+
"notes": "The body is the **raw file bytes**, not JSON, so it has no request schema; everything about the file — its kind, its name, its sync id and its announced size — rides in the `x-fleetless-asset-*` headers `ASSET_UPLOAD_HEADERS` names. The credential is a short-lived upload token minted by `POST /api/robots/:id/assets/sync`, verified in a `preParsing` hook so a refusal precedes the work rather than following it: a `preHandler` would already have buffered the whole file. **Nothing is refused for its own size** — the robot's asset store is the only limit, so the announced size is checked there against `ROBOT_ASSET_STORE_BYTES` and a file with no room left answers `409 quota_exceeded` carrying `store_bytes`, `used_bytes` and `size_bytes`, while the sync carries on with the next file. Past that, the server's own body limit answers a bare `413 bad_request` with none of those numbers in it. Rate limited per robot inside that same hook, which is why `rateLimited` is `false`: there is no rate-limiting preHandler registered on this route. The URDF itself is never refused for the store; only meshes and textures are charged against it."
|
|
4633
4843
|
},
|
|
4634
4844
|
{
|
|
4635
4845
|
"method": "GET",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"user_count": {
|
|
6
|
+
"type": "integer",
|
|
7
|
+
"minimum": 0,
|
|
8
|
+
"maximum": 9007199254740991,
|
|
9
|
+
"description": "App users deleted with the app. They are the developer's own customers, not Fleetless users, and exist in no other app."
|
|
10
|
+
},
|
|
11
|
+
"role_count": {
|
|
12
|
+
"type": "integer",
|
|
13
|
+
"minimum": 0,
|
|
14
|
+
"maximum": 9007199254740991,
|
|
15
|
+
"description": "Roles deleted with the app, each with its per-robot slug grants."
|
|
16
|
+
},
|
|
17
|
+
"server_key_count": {
|
|
18
|
+
"type": "integer",
|
|
19
|
+
"minimum": 0,
|
|
20
|
+
"maximum": 9007199254740991,
|
|
21
|
+
"description": "Server keys deleted with the app. A client still holding one is refused at its next request."
|
|
22
|
+
},
|
|
23
|
+
"invitation_count": {
|
|
24
|
+
"type": "integer",
|
|
25
|
+
"minimum": 0,
|
|
26
|
+
"maximum": 9007199254740991,
|
|
27
|
+
"description": "Outstanding invitations — unspent and unexpired — that will never be accepted."
|
|
28
|
+
},
|
|
29
|
+
"oidc_provider_count": {
|
|
30
|
+
"type": "integer",
|
|
31
|
+
"minimum": 0,
|
|
32
|
+
"maximum": 9007199254740991,
|
|
33
|
+
"description": "Identity providers configured for this app. The providers themselves are somebody else's; only this app's configuration of them goes."
|
|
34
|
+
},
|
|
35
|
+
"mail_template_count": {
|
|
36
|
+
"type": "integer",
|
|
37
|
+
"minimum": 0,
|
|
38
|
+
"maximum": 9007199254740991,
|
|
39
|
+
"description": "Custom mail templates, of at most three. A kind using the Fleetless default text is not counted — there is no row to lose."
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"required": [
|
|
43
|
+
"user_count",
|
|
44
|
+
"role_count",
|
|
45
|
+
"server_key_count",
|
|
46
|
+
"invitation_count",
|
|
47
|
+
"oidc_provider_count",
|
|
48
|
+
"mail_template_count"
|
|
49
|
+
],
|
|
50
|
+
"additionalProperties": false
|
|
51
|
+
}
|
|
@@ -24,10 +24,9 @@
|
|
|
24
24
|
"enum": [
|
|
25
25
|
"urdf",
|
|
26
26
|
"mesh",
|
|
27
|
-
"texture"
|
|
28
|
-
"other"
|
|
27
|
+
"texture"
|
|
29
28
|
],
|
|
30
|
-
"description": "What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with
|
|
29
|
+
"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."
|
|
31
30
|
},
|
|
32
31
|
"name": {
|
|
33
32
|
"type": "string",
|
|
@@ -128,32 +127,38 @@
|
|
|
128
127
|
"enum": [
|
|
129
128
|
"unresolvable",
|
|
130
129
|
"upload_failed",
|
|
131
|
-
"refused"
|
|
132
|
-
"too_large"
|
|
130
|
+
"refused"
|
|
133
131
|
],
|
|
134
|
-
"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
|
|
132
|
+
"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."
|
|
135
133
|
},
|
|
136
134
|
"details": {
|
|
137
|
-
"description": "The
|
|
135
|
+
"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.",
|
|
138
136
|
"anyOf": [
|
|
139
137
|
{
|
|
140
138
|
"type": "object",
|
|
141
139
|
"properties": {
|
|
142
|
-
"
|
|
140
|
+
"store_bytes": {
|
|
143
141
|
"type": "integer",
|
|
144
142
|
"exclusiveMinimum": 0,
|
|
145
143
|
"maximum": 9007199254740991,
|
|
146
|
-
"description": "The
|
|
144
|
+
"description": "The robot's store, in bytes."
|
|
145
|
+
},
|
|
146
|
+
"used_bytes": {
|
|
147
|
+
"type": "integer",
|
|
148
|
+
"minimum": 0,
|
|
149
|
+
"maximum": 9007199254740991,
|
|
150
|
+
"description": "Bytes the robot's assets occupy before this upload."
|
|
147
151
|
},
|
|
148
152
|
"size_bytes": {
|
|
149
153
|
"type": "integer",
|
|
150
154
|
"exclusiveMinimum": 0,
|
|
151
155
|
"maximum": 9007199254740991,
|
|
152
|
-
"description": "
|
|
156
|
+
"description": "The refused upload, in bytes."
|
|
153
157
|
}
|
|
154
158
|
},
|
|
155
159
|
"required": [
|
|
156
|
-
"
|
|
160
|
+
"store_bytes",
|
|
161
|
+
"used_bytes",
|
|
157
162
|
"size_bytes"
|
|
158
163
|
],
|
|
159
164
|
"additionalProperties": false
|
|
@@ -184,6 +189,25 @@
|
|
|
184
189
|
],
|
|
185
190
|
"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."
|
|
186
191
|
},
|
|
192
|
+
"stored": {
|
|
193
|
+
"anyOf": [
|
|
194
|
+
{
|
|
195
|
+
"type": "integer",
|
|
196
|
+
"minimum": 0,
|
|
197
|
+
"maximum": 9007199254740991
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"type": "null"
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
"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."
|
|
204
|
+
},
|
|
205
|
+
"announced": {
|
|
206
|
+
"type": "integer",
|
|
207
|
+
"minimum": 0,
|
|
208
|
+
"maximum": 9007199254740991,
|
|
209
|
+
"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."
|
|
210
|
+
},
|
|
187
211
|
"started_at": {
|
|
188
212
|
"type": "string",
|
|
189
213
|
"format": "date-time",
|
|
@@ -205,6 +229,8 @@
|
|
|
205
229
|
"total",
|
|
206
230
|
"failed",
|
|
207
231
|
"reason",
|
|
232
|
+
"stored",
|
|
233
|
+
"announced",
|
|
208
234
|
"started_at",
|
|
209
235
|
"updated_at"
|
|
210
236
|
],
|
|
@@ -276,13 +302,52 @@
|
|
|
276
302
|
}
|
|
277
303
|
],
|
|
278
304
|
"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."
|
|
305
|
+
},
|
|
306
|
+
"store": {
|
|
307
|
+
"type": "object",
|
|
308
|
+
"properties": {
|
|
309
|
+
"bytes": {
|
|
310
|
+
"type": "integer",
|
|
311
|
+
"exclusiveMinimum": 0,
|
|
312
|
+
"maximum": 9007199254740991,
|
|
313
|
+
"description": "The robot's asset store, `ROBOT_ASSET_STORE_BYTES`."
|
|
314
|
+
},
|
|
315
|
+
"used_bytes": {
|
|
316
|
+
"type": "integer",
|
|
317
|
+
"minimum": 0,
|
|
318
|
+
"maximum": 9007199254740991,
|
|
319
|
+
"description": "Bytes its assets occupy."
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
"required": [
|
|
323
|
+
"bytes",
|
|
324
|
+
"used_bytes"
|
|
325
|
+
],
|
|
326
|
+
"additionalProperties": false,
|
|
327
|
+
"description": "How full this robot's store is."
|
|
328
|
+
},
|
|
329
|
+
"joint_state_slug": {
|
|
330
|
+
"anyOf": [
|
|
331
|
+
{
|
|
332
|
+
"type": "string",
|
|
333
|
+
"minLength": 2,
|
|
334
|
+
"maxLength": 63,
|
|
335
|
+
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"type": "null"
|
|
339
|
+
}
|
|
340
|
+
],
|
|
341
|
+
"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`."
|
|
279
342
|
}
|
|
280
343
|
},
|
|
281
344
|
"required": [
|
|
282
345
|
"assets",
|
|
283
346
|
"active_sync",
|
|
284
347
|
"urdf",
|
|
285
|
-
"urdf_available"
|
|
348
|
+
"urdf_available",
|
|
349
|
+
"store",
|
|
350
|
+
"joint_state_slug"
|
|
286
351
|
],
|
|
287
352
|
"additionalProperties": false
|
|
288
353
|
}
|
|
@@ -52,32 +52,38 @@
|
|
|
52
52
|
"enum": [
|
|
53
53
|
"unresolvable",
|
|
54
54
|
"upload_failed",
|
|
55
|
-
"refused"
|
|
56
|
-
"too_large"
|
|
55
|
+
"refused"
|
|
57
56
|
],
|
|
58
|
-
"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
|
|
57
|
+
"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."
|
|
59
58
|
},
|
|
60
59
|
"details": {
|
|
61
|
-
"description": "The
|
|
60
|
+
"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.",
|
|
62
61
|
"anyOf": [
|
|
63
62
|
{
|
|
64
63
|
"type": "object",
|
|
65
64
|
"properties": {
|
|
66
|
-
"
|
|
65
|
+
"store_bytes": {
|
|
67
66
|
"type": "integer",
|
|
68
67
|
"exclusiveMinimum": 0,
|
|
69
68
|
"maximum": 9007199254740991,
|
|
70
|
-
"description": "The
|
|
69
|
+
"description": "The robot's store, in bytes."
|
|
70
|
+
},
|
|
71
|
+
"used_bytes": {
|
|
72
|
+
"type": "integer",
|
|
73
|
+
"minimum": 0,
|
|
74
|
+
"maximum": 9007199254740991,
|
|
75
|
+
"description": "Bytes the robot's assets occupy before this upload."
|
|
71
76
|
},
|
|
72
77
|
"size_bytes": {
|
|
73
78
|
"type": "integer",
|
|
74
79
|
"exclusiveMinimum": 0,
|
|
75
80
|
"maximum": 9007199254740991,
|
|
76
|
-
"description": "
|
|
81
|
+
"description": "The refused upload, in bytes."
|
|
77
82
|
}
|
|
78
83
|
},
|
|
79
84
|
"required": [
|
|
80
|
-
"
|
|
85
|
+
"store_bytes",
|
|
86
|
+
"used_bytes",
|
|
81
87
|
"size_bytes"
|
|
82
88
|
],
|
|
83
89
|
"additionalProperties": false
|
|
@@ -108,6 +114,25 @@
|
|
|
108
114
|
],
|
|
109
115
|
"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."
|
|
110
116
|
},
|
|
117
|
+
"stored": {
|
|
118
|
+
"anyOf": [
|
|
119
|
+
{
|
|
120
|
+
"type": "integer",
|
|
121
|
+
"minimum": 0,
|
|
122
|
+
"maximum": 9007199254740991
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"type": "null"
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"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."
|
|
129
|
+
},
|
|
130
|
+
"announced": {
|
|
131
|
+
"type": "integer",
|
|
132
|
+
"minimum": 0,
|
|
133
|
+
"maximum": 9007199254740991,
|
|
134
|
+
"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."
|
|
135
|
+
},
|
|
111
136
|
"started_at": {
|
|
112
137
|
"type": "string",
|
|
113
138
|
"format": "date-time",
|
|
@@ -129,6 +154,8 @@
|
|
|
129
154
|
"total",
|
|
130
155
|
"failed",
|
|
131
156
|
"reason",
|
|
157
|
+
"stored",
|
|
158
|
+
"announced",
|
|
132
159
|
"started_at",
|
|
133
160
|
"updated_at"
|
|
134
161
|
],
|
|
@@ -19,10 +19,9 @@
|
|
|
19
19
|
"enum": [
|
|
20
20
|
"urdf",
|
|
21
21
|
"mesh",
|
|
22
|
-
"texture"
|
|
23
|
-
"other"
|
|
22
|
+
"texture"
|
|
24
23
|
],
|
|
25
|
-
"description": "What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with
|
|
24
|
+
"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."
|
|
26
25
|
},
|
|
27
26
|
"name": {
|
|
28
27
|
"type": "string",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"deleted": {
|
|
6
|
+
"type": "integer",
|
|
7
|
+
"minimum": 0,
|
|
8
|
+
"maximum": 9007199254740991,
|
|
9
|
+
"description": "How many assets — URDF, meshes and textures together — were removed."
|
|
10
|
+
},
|
|
11
|
+
"bytes_freed": {
|
|
12
|
+
"type": "integer",
|
|
13
|
+
"minimum": 0,
|
|
14
|
+
"maximum": 9007199254740991,
|
|
15
|
+
"description": "The bytes the robot's store got back."
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"required": [
|
|
19
|
+
"deleted",
|
|
20
|
+
"bytes_freed"
|
|
21
|
+
],
|
|
22
|
+
"additionalProperties": false
|
|
23
|
+
}
|