@fleetless/contracts 1.0.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -2
- package/CONTRIBUTING.md +100 -75
- package/README.md +69 -83
- package/SECURITY.md +24 -24
- package/artifacts/openapi.json +359 -65
- package/artifacts/routes.json +74 -3
- package/artifacts/schema/app-list-response.schema.json +2 -2
- package/artifacts/schema/app-oidc-provider-list-response.schema.json +1 -1
- package/artifacts/schema/app-oidc-provider.schema.json +1 -1
- package/artifacts/schema/app-user-list-response.schema.json +2 -2
- package/artifacts/schema/app-user.schema.json +2 -2
- package/artifacts/schema/app.schema.json +2 -2
- package/artifacts/schema/asset-list-response.schema.json +7 -7
- package/artifacts/schema/asset-sync-request.schema.json +1 -1
- package/artifacts/schema/asset-sync-status.schema.json +1 -1
- package/artifacts/schema/asset.schema.json +3 -3
- package/artifacts/schema/auth-me-response.schema.json +2 -2
- package/artifacts/schema/auth-ok.schema.json +1 -1
- package/artifacts/schema/authorization-server-metadata.schema.json +1 -1
- package/artifacts/schema/bridge-asset-progress.schema.json +1 -1
- package/artifacts/schema/busy-details.schema.json +3 -3
- package/artifacts/schema/client-identity.schema.json +1 -1
- package/artifacts/schema/client-login-request.schema.json +1 -1
- package/artifacts/schema/client-logout-request.schema.json +1 -1
- package/artifacts/schema/client-mcp-interaction.schema.json +1 -1
- package/artifacts/schema/client-robot-list-item.schema.json +70 -0
- package/artifacts/schema/client-robot-list-response.schema.json +83 -0
- package/artifacts/schema/cloud-config.schema.json +1 -1
- package/artifacts/schema/command-result.schema.json +3 -3
- package/artifacts/schema/config-draft-response.schema.json +1 -1
- package/artifacts/schema/config-version-response.schema.json +1 -1
- package/artifacts/schema/create-server-key-response.schema.json +1 -1
- package/artifacts/schema/datapoint-config.schema.json +1 -1
- package/artifacts/schema/datapoint-value.schema.json +2 -2
- package/artifacts/schema/dynamic-client-registration-request.schema.json +2 -2
- package/artifacts/schema/fleetless-user-list-response.schema.json +2 -2
- package/artifacts/schema/fleetless-user.schema.json +2 -2
- package/artifacts/schema/invoke-or-service-response.schema.json +4 -4
- package/artifacts/schema/invoke-response.schema.json +3 -3
- package/artifacts/schema/job-actor.schema.json +1 -1
- package/artifacts/schema/job-event.schema.json +3 -3
- package/artifacts/schema/job-response.schema.json +3 -3
- package/artifacts/schema/job-run-list-response.schema.json +2 -2
- package/artifacts/schema/job-run.schema.json +2 -2
- package/artifacts/schema/job.schema.json +3 -3
- package/artifacts/schema/mcp-consent-grant-list-response.schema.json +2 -2
- package/artifacts/schema/mcp-consent-grant.schema.json +2 -2
- package/artifacts/schema/oauth-authorize-query.schema.json +1 -1
- package/artifacts/schema/oauth-token-request.schema.json +1 -1
- package/artifacts/schema/patch-org-response.schema.json +1 -1
- package/artifacts/schema/patch-robot-response.schema.json +1 -1
- package/artifacts/schema/robot-config-doc.schema.json +1 -1
- package/artifacts/schema/robot-jobs-response.schema.json +3 -3
- package/artifacts/schema/role-list-response.schema.json +1 -1
- package/artifacts/schema/role.schema.json +1 -1
- package/artifacts/schema/server-key-list-response.schema.json +2 -2
- package/artifacts/schema/server-key.schema.json +1 -1
- package/artifacts/schema/service-call-response.schema.json +1 -1
- package/artifacts/schema/sign-up-response.schema.json +2 -2
- package/artifacts/schema/urdf-completeness.schema.json +2 -2
- package/artifacts/schema-outgoing/bridge-asset-progress.schema.json +1 -1
- package/dist/alerts.d.ts +15 -17
- package/dist/alerts.js +15 -17
- package/dist/app-users.d.ts +5 -6
- package/dist/app-users.js +9 -10
- package/dist/apps.d.ts +5 -6
- package/dist/apps.js +11 -12
- package/dist/assets.js +10 -13
- package/dist/audit.d.ts +10 -12
- package/dist/audit.js +14 -17
- package/dist/client-auth.d.ts +8 -9
- package/dist/client-auth.js +14 -15
- package/dist/client-robots.d.ts +37 -0
- package/dist/client-robots.js +30 -0
- package/dist/config-issues.d.ts +3 -3
- package/dist/config-issues.js +3 -3
- package/dist/config.d.ts +5 -6
- package/dist/config.js +7 -8
- package/dist/errors.d.ts +4 -4
- package/dist/errors.js +8 -9
- package/dist/identity.d.ts +4 -5
- package/dist/identity.js +7 -8
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/jobs.js +5 -5
- package/dist/mcp.d.ts +11 -9
- package/dist/mcp.js +8 -3
- package/dist/oauth.d.ts +8 -10
- package/dist/oauth.js +13 -15
- package/dist/protocol.d.ts +7 -8
- package/dist/protocol.js +20 -22
- package/dist/realtime.d.ts +2 -2
- package/dist/realtime.js +4 -4
- package/dist/rest.js +4 -4
- package/dist/routes.js +40 -4
- package/package.json +1 -1
package/artifacts/routes.json
CHANGED
|
@@ -273,6 +273,24 @@
|
|
|
273
273
|
"transport": "http",
|
|
274
274
|
"notes": "HTML. An unknown, spent or expired token renders one \"link no longer valid\" page at `410` — they are one refusal on the wire already, and splitting them here would tell a stranger which tokens ever existed. No rate limiter: the GET changes nothing, and the POST it leads to is limited per IP."
|
|
275
275
|
},
|
|
276
|
+
{
|
|
277
|
+
"method": "GET",
|
|
278
|
+
"path": "/favicon.svg",
|
|
279
|
+
"section": "client-auth",
|
|
280
|
+
"summary": "Serves the Fleetless icon for the auth portal's and the MCP welcome page's browser tab.",
|
|
281
|
+
"audience": "internal",
|
|
282
|
+
"auth": "none",
|
|
283
|
+
"rateLimited": false,
|
|
284
|
+
"ownerTier": false,
|
|
285
|
+
"status": 200,
|
|
286
|
+
"params": [],
|
|
287
|
+
"query": null,
|
|
288
|
+
"request": null,
|
|
289
|
+
"response": null,
|
|
290
|
+
"errors": [],
|
|
291
|
+
"transport": "http",
|
|
292
|
+
"notes": "An SVG, not JSON. Those pages carry a Content-Security-Policy that admits no `data:` image, so the icon is a file on their own origin — the one source `img-src 'self'` names. Cached for a day: the bytes change when the brand does, not per deploy."
|
|
293
|
+
},
|
|
276
294
|
{
|
|
277
295
|
"method": "POST",
|
|
278
296
|
"path": "/api/auth/password/reset/confirm",
|
|
@@ -1006,7 +1024,7 @@
|
|
|
1006
1024
|
},
|
|
1007
1025
|
{
|
|
1008
1026
|
"name": "clientId",
|
|
1009
|
-
"description": "The MCP client, as `GET /api/apps/:id/users/:userId/mcp-grants` reports its `client_id`. Not a uuid —
|
|
1027
|
+
"description": "The MCP client, as `GET /api/apps/:id/users/:userId/mcp-grants` reports its `client_id`. Not a uuid — the identifier dynamic registration issued."
|
|
1010
1028
|
}
|
|
1011
1029
|
],
|
|
1012
1030
|
"query": null,
|
|
@@ -2995,7 +3013,7 @@
|
|
|
2995
3013
|
"params": [
|
|
2996
3014
|
{
|
|
2997
3015
|
"name": "clientId",
|
|
2998
|
-
"description": "The MCP client, as `GET /api/client/mcp/grants` reports its `client_id`. Not a uuid —
|
|
3016
|
+
"description": "The MCP client, as `GET /api/client/mcp/grants` reports its `client_id`. Not a uuid — the identifier dynamic registration issued."
|
|
2999
3017
|
}
|
|
3000
3018
|
],
|
|
3001
3019
|
"query": null,
|
|
@@ -3300,6 +3318,59 @@
|
|
|
3300
3318
|
"transport": "http",
|
|
3301
3319
|
"notes": "For a client caller the grant check runs **before** any existence lookup, with no extra query on either path to time: a denied slug and a nonexistent one must be one answer. That is why an ungranted slug is `403 forbidden` while a granted-but-unconfigured one is `404 unknown_datapoint` and a configured one with no sample yet is `404 no_data` — three facts a caller who is entitled to them needs told apart. The plane built-ins (`bridge_state`, `robot_details`) answer here too, without appearing in any document."
|
|
3302
3320
|
},
|
|
3321
|
+
{
|
|
3322
|
+
"method": "GET",
|
|
3323
|
+
"path": "/api/client/robots",
|
|
3324
|
+
"section": "robots",
|
|
3325
|
+
"summary": "Lists the robots the caller reaches, with bridge state and the published configuration version.",
|
|
3326
|
+
"audience": "client",
|
|
3327
|
+
"auth": "developer_or_client",
|
|
3328
|
+
"rateLimited": false,
|
|
3329
|
+
"ownerTier": false,
|
|
3330
|
+
"status": 200,
|
|
3331
|
+
"params": [],
|
|
3332
|
+
"query": null,
|
|
3333
|
+
"request": null,
|
|
3334
|
+
"response": "client-robot-list-response",
|
|
3335
|
+
"errors": [
|
|
3336
|
+
"unauthorized",
|
|
3337
|
+
"token_expired",
|
|
3338
|
+
"token_revoked",
|
|
3339
|
+
"forbidden"
|
|
3340
|
+
],
|
|
3341
|
+
"transport": "http",
|
|
3342
|
+
"notes": "**The REST twin of the MCP tool `robots_list`**, and the one robot question no robot-scoped route can answer: which robots may I name at all. An app user sees the robots their app attaches on which their role grants at least one slug or capability; a server key sees every robot its app attaches; a developer bearer sees the organisation's robots. Name order, id as the tiebreak. A robot on which the role grants nothing is absent rather than listed empty — the same answer `robots_list` gives, for the same reason: reach is a grant, not an attachment. Under `/api/client/` because it names no robot; every robot-scoped read stays under `/api/robots/:id/…`."
|
|
3343
|
+
},
|
|
3344
|
+
{
|
|
3345
|
+
"method": "GET",
|
|
3346
|
+
"path": "/api/robots/:id/datasheet",
|
|
3347
|
+
"section": "robots",
|
|
3348
|
+
"summary": "Describes everything the caller's role lets them do on one robot, with parameter schemas.",
|
|
3349
|
+
"audience": "client",
|
|
3350
|
+
"auth": "developer_or_client",
|
|
3351
|
+
"rateLimited": false,
|
|
3352
|
+
"ownerTier": false,
|
|
3353
|
+
"status": 200,
|
|
3354
|
+
"params": [
|
|
3355
|
+
{
|
|
3356
|
+
"name": "id",
|
|
3357
|
+
"description": "The robot's uuid, as `GET /api/client/robots` lists it."
|
|
3358
|
+
}
|
|
3359
|
+
],
|
|
3360
|
+
"query": null,
|
|
3361
|
+
"request": null,
|
|
3362
|
+
"response": "mcp-robot-datasheet",
|
|
3363
|
+
"errors": [
|
|
3364
|
+
"unauthorized",
|
|
3365
|
+
"token_expired",
|
|
3366
|
+
"token_revoked",
|
|
3367
|
+
"forbidden",
|
|
3368
|
+
"invalid_uuid",
|
|
3369
|
+
"not_found"
|
|
3370
|
+
],
|
|
3371
|
+
"transport": "http",
|
|
3372
|
+
"notes": "**The REST twin of the MCP tool `robot_describe`**: one answer per robot — every datapoint, action, service, publisher and camera the role grants, each with its `input_schema` where it takes parameters, plus the two capabilities that gate whole features, `action_history` and `assets`. A robot with nothing published answers an empty `exposures` list, never a refusal. A robot the caller does not reach — not attached to their app, or attached with a role that grants nothing on it — answers `404` exactly as one that does not exist. The app-user datapoint and camera listings under this prefix stay; this is the one read that also names actions, services, publishers and capabilities, which is what an app needs before it can draw a screen."
|
|
3373
|
+
},
|
|
3303
3374
|
{
|
|
3304
3375
|
"method": "GET",
|
|
3305
3376
|
"path": "/api/robots/:id/config/draft",
|
|
@@ -3807,7 +3878,7 @@
|
|
|
3807
3878
|
},
|
|
3808
3879
|
{
|
|
3809
3880
|
"name": "slug",
|
|
3810
|
-
"description": "The action or service slug from the published configuration
|
|
3881
|
+
"description": "The action or service slug from the published configuration — the cloud already knows which kind."
|
|
3811
3882
|
}
|
|
3812
3883
|
],
|
|
3813
3884
|
"query": null,
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"minLength": 2,
|
|
31
31
|
"maxLength": 63,
|
|
32
32
|
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
|
|
33
|
-
"description": "The stable handle a client sends at login, lowercase and underscore-separated. **Globally unique, not per organisation** — `clientLoginRequest` carries no org context
|
|
33
|
+
"description": "The stable handle a client sends at login, lowercase and underscore-separated. **Globally unique, not per organisation** — `clientLoginRequest` carries no org context, so a collision is refused with `identifier_taken`."
|
|
34
34
|
},
|
|
35
35
|
"robot_ids": {
|
|
36
36
|
"type": "array",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"type": "null"
|
|
53
53
|
}
|
|
54
54
|
],
|
|
55
|
-
"description": "The role an app user gets when
|
|
55
|
+
"description": "The role an app user gets when created or invited without an explicit one. `null` means this app has not chosen a default, the normal state of an app created before its roles were configured — and then a create or invite that omits `role_id` gets `validation_error`, not a user with no role. An invitation resolves the role when issued, so changing this never re-aims an outstanding one. The role must belong to this app, which `PATCH /api/apps/:id` checks and the schema cannot."
|
|
56
56
|
},
|
|
57
57
|
"created_at": {
|
|
58
58
|
"type": "string",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"enabled": {
|
|
62
62
|
"type": "boolean",
|
|
63
|
-
"description": "Whether this provider is offered
|
|
63
|
+
"description": "Whether this provider is offered. A disabled provider disappears from `GET /api/client/providers` and refuses a start with `provider_disabled`, without the row and its linked identities being deleted."
|
|
64
64
|
},
|
|
65
65
|
"created_at": {
|
|
66
66
|
"type": "string",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"enabled": {
|
|
57
57
|
"type": "boolean",
|
|
58
|
-
"description": "Whether this provider is offered
|
|
58
|
+
"description": "Whether this provider is offered. A disabled provider disappears from `GET /api/client/providers` and refuses a start with `provider_disabled`, without the row and its linked identities being deleted."
|
|
59
59
|
},
|
|
60
60
|
"created_at": {
|
|
61
61
|
"type": "string",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"has_password": {
|
|
57
57
|
"type": "boolean",
|
|
58
|
-
"description": "Whether this account has a Fleetless-held password
|
|
58
|
+
"description": "Whether this account has a Fleetless-held password. `false` is an identity-provider-only account, or an invitation not yet accepted — it does not mean blocked and it does not mean without access. No hash, no algorithm and no \"last changed\" travels here, and nothing on the wire can say whether a password is strong or already known to somebody else."
|
|
59
59
|
},
|
|
60
60
|
"providers": {
|
|
61
61
|
"maxItems": 20,
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"maxLength": 40,
|
|
66
66
|
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"
|
|
67
67
|
},
|
|
68
|
-
"description": "The slugs of the identity providers this account is linked to, empty for a password-only user.
|
|
68
|
+
"description": "The slugs of the identity providers this account is linked to, empty for a password-only user. Lets a developer's user list say where an account came from without a second request."
|
|
69
69
|
},
|
|
70
70
|
"last_login_at": {
|
|
71
71
|
"anyOf": [
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"has_password": {
|
|
52
52
|
"type": "boolean",
|
|
53
|
-
"description": "Whether this account has a Fleetless-held password
|
|
53
|
+
"description": "Whether this account has a Fleetless-held password. `false` is an identity-provider-only account, or an invitation not yet accepted — it does not mean blocked and it does not mean without access. No hash, no algorithm and no \"last changed\" travels here, and nothing on the wire can say whether a password is strong or already known to somebody else."
|
|
54
54
|
},
|
|
55
55
|
"providers": {
|
|
56
56
|
"maxItems": 20,
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"maxLength": 40,
|
|
61
61
|
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"
|
|
62
62
|
},
|
|
63
|
-
"description": "The slugs of the identity providers this account is linked to, empty for a password-only user.
|
|
63
|
+
"description": "The slugs of the identity providers this account is linked to, empty for a password-only user. Lets a developer's user list say where an account came from without a second request."
|
|
64
64
|
},
|
|
65
65
|
"last_login_at": {
|
|
66
66
|
"anyOf": [
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"minLength": 2,
|
|
26
26
|
"maxLength": 63,
|
|
27
27
|
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
|
|
28
|
-
"description": "The stable handle a client sends at login, lowercase and underscore-separated. **Globally unique, not per organisation** — `clientLoginRequest` carries no org context
|
|
28
|
+
"description": "The stable handle a client sends at login, lowercase and underscore-separated. **Globally unique, not per organisation** — `clientLoginRequest` carries no org context, so a collision is refused with `identifier_taken`."
|
|
29
29
|
},
|
|
30
30
|
"robot_ids": {
|
|
31
31
|
"type": "array",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"type": "null"
|
|
48
48
|
}
|
|
49
49
|
],
|
|
50
|
-
"description": "The role an app user gets when
|
|
50
|
+
"description": "The role an app user gets when created or invited without an explicit one. `null` means this app has not chosen a default, the normal state of an app created before its roles were configured — and then a create or invite that omits `role_id` gets `validation_error`, not a user with no role. An invitation resolves the role when issued, so changing this never re-aims an outstanding one. The role must belong to this app, which `PATCH /api/apps/:id` checks and the schema cannot."
|
|
51
51
|
},
|
|
52
52
|
"created_at": {
|
|
53
53
|
"type": "string",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"texture",
|
|
28
28
|
"other"
|
|
29
29
|
],
|
|
30
|
-
"description": "What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with, or `other`. A renderer decides from this alone, before fetching anything, what
|
|
30
|
+
"description": "What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with, or `other`. A renderer decides from this alone, before fetching anything, what to pre-fetch."
|
|
31
31
|
},
|
|
32
32
|
"name": {
|
|
33
33
|
"type": "string",
|
|
34
34
|
"minLength": 1,
|
|
35
35
|
"maxLength": 500,
|
|
36
|
-
"description": "What the robot called it — for a mesh, the `package://` URI the URDF references, verbatim,
|
|
36
|
+
"description": "What the robot called it — for a mesh, the `package://` URI the URDF references, verbatim, the only string a developer can match against their own workspace. A file the URDF never names (an image a `.dae` loads for itself) is named by joining the mesh's own directory with that internal reference."
|
|
37
37
|
},
|
|
38
38
|
"media_type": {
|
|
39
39
|
"type": "string",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"sha256": {
|
|
51
51
|
"type": "string",
|
|
52
52
|
"pattern": "^[a-f0-9]{64}$",
|
|
53
|
-
"description": "The content hash, lowercase hex
|
|
53
|
+
"description": "The content hash, lowercase hex. Exposed because it is the only way a client can tell \"this is the same mesh I already have\" across robots — the reason two robots sharing a mesh cost one copy."
|
|
54
54
|
},
|
|
55
55
|
"created_at": {
|
|
56
56
|
"type": "string",
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
"type": "integer",
|
|
150
150
|
"exclusiveMinimum": 0,
|
|
151
151
|
"maximum": 9007199254740991,
|
|
152
|
-
"description": "How large the refused file
|
|
152
|
+
"description": "How large the refused file is, in bytes. With `limit_bytes` beside it a developer can tell whether to shrink the mesh or raise the limit; \"too large\" alone answers neither."
|
|
153
153
|
}
|
|
154
154
|
},
|
|
155
155
|
"required": [
|
|
@@ -238,7 +238,7 @@
|
|
|
238
238
|
"type": "string",
|
|
239
239
|
"minLength": 1,
|
|
240
240
|
"maxLength": 500,
|
|
241
|
-
"description": "The reference, verbatim, that no stored asset answers — a `package://` URI the workspace does not hold, or an absolute or bare relative path nothing will ever fetch.
|
|
241
|
+
"description": "The reference, verbatim, that no stored asset answers — a `package://` URI the workspace does not hold, or an absolute or bare relative path nothing will ever fetch."
|
|
242
242
|
},
|
|
243
243
|
"element": {
|
|
244
244
|
"type": "string",
|
|
@@ -255,7 +255,7 @@
|
|
|
255
255
|
],
|
|
256
256
|
"additionalProperties": false
|
|
257
257
|
},
|
|
258
|
-
"description": "The references nothing in the store answers, each with the element that asked for it. A bare count
|
|
258
|
+
"description": "The references nothing in the store answers, each with the element that asked for it. A bare count would send a developer hunting through the workspace by hand; the references are what they can act on."
|
|
259
259
|
}
|
|
260
260
|
},
|
|
261
261
|
"required": [
|
|
@@ -275,7 +275,7 @@
|
|
|
275
275
|
"type": "null"
|
|
276
276
|
}
|
|
277
277
|
],
|
|
278
|
-
"description": "What the connected bridge says it *could* transfer
|
|
278
|
+
"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."
|
|
279
279
|
}
|
|
280
280
|
},
|
|
281
281
|
"required": [
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"enum": [
|
|
8
8
|
"bridge"
|
|
9
9
|
],
|
|
10
|
-
"description": "Where the bytes come from. `bridge` is the only value today: the connected bridge reads them from the robot's own workspace.
|
|
10
|
+
"description": "Where the bytes come from. `bridge` is the only value today: the connected bridge reads them from the robot's own workspace. Validated rather than ignored, so a caller naming an unknown source is told so instead of silently getting a bridge sync."
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"required": [
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"type": "integer",
|
|
74
74
|
"exclusiveMinimum": 0,
|
|
75
75
|
"maximum": 9007199254740991,
|
|
76
|
-
"description": "How large the refused file
|
|
76
|
+
"description": "How large the refused file is, in bytes. With `limit_bytes` beside it a developer can tell whether to shrink the mesh or raise the limit; \"too large\" alone answers neither."
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
79
|
"required": [
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"texture",
|
|
23
23
|
"other"
|
|
24
24
|
],
|
|
25
|
-
"description": "What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with, or `other`. A renderer decides from this alone, before fetching anything, what
|
|
25
|
+
"description": "What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with, or `other`. A renderer decides from this alone, before fetching anything, what to pre-fetch."
|
|
26
26
|
},
|
|
27
27
|
"name": {
|
|
28
28
|
"type": "string",
|
|
29
29
|
"minLength": 1,
|
|
30
30
|
"maxLength": 500,
|
|
31
|
-
"description": "What the robot called it — for a mesh, the `package://` URI the URDF references, verbatim,
|
|
31
|
+
"description": "What the robot called it — for a mesh, the `package://` URI the URDF references, verbatim, the only string a developer can match against their own workspace. A file the URDF never names (an image a `.dae` loads for itself) is named by joining the mesh's own directory with that internal reference."
|
|
32
32
|
},
|
|
33
33
|
"media_type": {
|
|
34
34
|
"type": "string",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"sha256": {
|
|
46
46
|
"type": "string",
|
|
47
47
|
"pattern": "^[a-f0-9]{64}$",
|
|
48
|
-
"description": "The content hash, lowercase hex
|
|
48
|
+
"description": "The content hash, lowercase hex. Exposed because it is the only way a client can tell \"this is the same mesh I already have\" across robots — the reason two robots sharing a mesh cost one copy."
|
|
49
49
|
},
|
|
50
50
|
"created_at": {
|
|
51
51
|
"type": "string",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"type": "string",
|
|
45
45
|
"format": "uuid",
|
|
46
46
|
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
|
|
47
|
-
"description": "The organisation this person belongs to. Every developer route is already scoped to the caller's org, so this confirms what a client is looking at
|
|
47
|
+
"description": "The organisation this person belongs to. Every developer route is already scoped to the caller's org, so this confirms what a client is looking at, not a filter it applies."
|
|
48
48
|
},
|
|
49
49
|
"email": {
|
|
50
50
|
"type": "string",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"owner",
|
|
72
72
|
"developer"
|
|
73
73
|
],
|
|
74
|
-
"description": "The console powers this person holds. **Required** — every Fleetless user
|
|
74
|
+
"description": "The console powers this person holds. **Required** — every Fleetless user has a tier; it was optional only while the org also held people with no console powers to grade, and that pool is gone."
|
|
75
75
|
},
|
|
76
76
|
"created_at": {
|
|
77
77
|
"type": "string",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"app_user",
|
|
17
17
|
"server_key"
|
|
18
18
|
],
|
|
19
|
-
"description": "Which of the three kinds of caller this is: a `developer` working through the console, an `app_user` holding a token from a client login, or a `server_key` used by server-side code. Stated outright
|
|
19
|
+
"description": "Which of the three kinds of caller this is: a `developer` working through the console, an `app_user` holding a token from a client login, or a `server_key` used by server-side code. Stated outright, not inferred from which id is set."
|
|
20
20
|
},
|
|
21
21
|
"developer_id": {
|
|
22
22
|
"anyOf": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"authorization_endpoint": {
|
|
11
11
|
"type": "string",
|
|
12
12
|
"format": "uri",
|
|
13
|
-
"description": "
|
|
13
|
+
"description": "Where a client sends the user to authorize."
|
|
14
14
|
},
|
|
15
15
|
"token_endpoint": {
|
|
16
16
|
"type": "string",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"type": "integer",
|
|
60
60
|
"exclusiveMinimum": 0,
|
|
61
61
|
"maximum": 9007199254740991,
|
|
62
|
-
"description": "How large the refused file
|
|
62
|
+
"description": "How large the refused file is, in bytes. With `limit_bytes` beside it a developer can tell whether to shrink the mesh or raise the limit; \"too large\" alone answers neither."
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
"required": [
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"type": "string",
|
|
10
10
|
"format": "uuid",
|
|
11
11
|
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
|
|
12
|
-
"description": "The job's id, minted by the cloud when the invocation is accepted. Informative
|
|
12
|
+
"description": "The job's id, minted by the cloud when the invocation is accepted. Informative — state is observed by slug; a cancel names this id to stop one specific job rather than whatever is running."
|
|
13
13
|
},
|
|
14
14
|
"robot_id": {
|
|
15
15
|
"type": "string",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"cancelled",
|
|
34
34
|
"lost"
|
|
35
35
|
],
|
|
36
|
-
"description": "Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `lost` is a real outcome — the bridge restarted mid-job and the result is gone —
|
|
36
|
+
"description": "Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `lost` is a real outcome — the bridge restarted mid-job and the result is gone — stated rather than left reading `running` by default."
|
|
37
37
|
},
|
|
38
38
|
"started_at": {
|
|
39
39
|
"type": "string",
|
|
40
40
|
"format": "date-time",
|
|
41
41
|
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
|
42
|
-
"description": "When the cloud minted this job, as an ISO 8601 timestamp. For a job adopted from a reconnecting bridge
|
|
42
|
+
"description": "When the cloud minted this job, as an ISO 8601 timestamp. For a job adopted from a reconnecting bridge, this is **adoption time**, not the real start — the cloud never minted it."
|
|
43
43
|
},
|
|
44
44
|
"updated_at": {
|
|
45
45
|
"type": "string",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"app_user",
|
|
10
10
|
"server_key"
|
|
11
11
|
],
|
|
12
|
-
"description": "Which of the three kinds of caller this is: a `developer` working through the console, an `app_user` holding a token from a client login, or a `server_key` used by server-side code. Stated outright
|
|
12
|
+
"description": "Which of the three kinds of caller this is: a `developer` working through the console, an `app_user` holding a token from a client login, or a `server_key` used by server-side code. Stated outright, not inferred from which id is set."
|
|
13
13
|
},
|
|
14
14
|
"developer_id": {
|
|
15
15
|
"anyOf": [
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"minLength": 2,
|
|
8
8
|
"maxLength": 63,
|
|
9
9
|
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
|
|
10
|
-
"description": "The app being logged in to
|
|
10
|
+
"description": "The app being logged in to: its globally unique, lowercase, underscore-separated identifier, chosen by the developer at creation. There is no organisation context at login, so this is what decides which app the credentials are checked for."
|
|
11
11
|
},
|
|
12
12
|
"email": {
|
|
13
13
|
"type": "string",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"refresh_token": {
|
|
6
6
|
"type": "string",
|
|
7
7
|
"minLength": 1,
|
|
8
|
-
"description": "Any refresh token of the session to end. The whole token family is revoked server-side, so a token stolen before this call stops working too — clearing a client-side store is a gesture, not a revocation. The answer is `204`: a token the server does not recognise gets it too, since the end state
|
|
8
|
+
"description": "Any refresh token of the session to end. The whole token family is revoked server-side, so a token stolen before this call stops working too — clearing a client-side store is a gesture, not a revocation. The answer is `204`: a token the server does not recognise gets it too, since that is the end state being asked for."
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"required": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"client_name_verified": {
|
|
27
27
|
"type": "boolean",
|
|
28
28
|
"const": false,
|
|
29
|
-
"description": "Always `false`. The client registered itself without authentication and
|
|
29
|
+
"description": "Always `false`. The client registered itself without authentication and named itself, so it must be rendered as a claim and never as an identity. There is no verified case, which is why this is a literal and not a boolean: a `true` branch would be dead code that looked like a safeguard."
|
|
30
30
|
},
|
|
31
31
|
"scopes": {
|
|
32
32
|
"type": "array",
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"id": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"format": "uuid",
|
|
8
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
|
|
9
|
+
"description": "The robot, and what every robot-scoped route takes as its `:id`."
|
|
10
|
+
},
|
|
11
|
+
"name": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"minLength": 1,
|
|
14
|
+
"maxLength": 63,
|
|
15
|
+
"description": "The robot's display name, at most 63 characters. Free text, changed through `PATCH /api/robots/:id`."
|
|
16
|
+
},
|
|
17
|
+
"created_at": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"format": "date-time",
|
|
20
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
|
21
|
+
"description": "When the robot was created, as an ISO 8601 timestamp."
|
|
22
|
+
},
|
|
23
|
+
"bridge_state": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"online": {
|
|
27
|
+
"type": "boolean"
|
|
28
|
+
},
|
|
29
|
+
"latency_ms": {
|
|
30
|
+
"anyOf": [
|
|
31
|
+
{
|
|
32
|
+
"type": "number",
|
|
33
|
+
"minimum": 0
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"type": "null"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"required": [
|
|
42
|
+
"online",
|
|
43
|
+
"latency_ms"
|
|
44
|
+
],
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"description": "The built-in `bridge_state` datapoint as the cloud observes it right now: whether the bridge is connected, and its latency when it is."
|
|
47
|
+
},
|
|
48
|
+
"published_version": {
|
|
49
|
+
"anyOf": [
|
|
50
|
+
{
|
|
51
|
+
"type": "integer",
|
|
52
|
+
"exclusiveMinimum": 0,
|
|
53
|
+
"maximum": 9007199254740991
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"type": "null"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"description": "The published configuration version, or `null` when nothing has been published yet. A robot with nothing published is still listed — \"not configured yet\" is a real state, and the caller is entitled to it — and its datasheet answers an empty exposure list."
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"required": [
|
|
63
|
+
"id",
|
|
64
|
+
"name",
|
|
65
|
+
"created_at",
|
|
66
|
+
"bridge_state",
|
|
67
|
+
"published_version"
|
|
68
|
+
],
|
|
69
|
+
"additionalProperties": false
|
|
70
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"robots": {
|
|
6
|
+
"type": "array",
|
|
7
|
+
"items": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"id": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"format": "uuid",
|
|
13
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
|
|
14
|
+
"description": "The robot, and what every robot-scoped route takes as its `:id`."
|
|
15
|
+
},
|
|
16
|
+
"name": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"minLength": 1,
|
|
19
|
+
"maxLength": 63,
|
|
20
|
+
"description": "The robot's display name, at most 63 characters. Free text, changed through `PATCH /api/robots/:id`."
|
|
21
|
+
},
|
|
22
|
+
"created_at": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"format": "date-time",
|
|
25
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
|
26
|
+
"description": "When the robot was created, as an ISO 8601 timestamp."
|
|
27
|
+
},
|
|
28
|
+
"bridge_state": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {
|
|
31
|
+
"online": {
|
|
32
|
+
"type": "boolean"
|
|
33
|
+
},
|
|
34
|
+
"latency_ms": {
|
|
35
|
+
"anyOf": [
|
|
36
|
+
{
|
|
37
|
+
"type": "number",
|
|
38
|
+
"minimum": 0
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "null"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"required": [
|
|
47
|
+
"online",
|
|
48
|
+
"latency_ms"
|
|
49
|
+
],
|
|
50
|
+
"additionalProperties": false,
|
|
51
|
+
"description": "The built-in `bridge_state` datapoint as the cloud observes it right now: whether the bridge is connected, and its latency when it is."
|
|
52
|
+
},
|
|
53
|
+
"published_version": {
|
|
54
|
+
"anyOf": [
|
|
55
|
+
{
|
|
56
|
+
"type": "integer",
|
|
57
|
+
"exclusiveMinimum": 0,
|
|
58
|
+
"maximum": 9007199254740991
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"type": "null"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"description": "The published configuration version, or `null` when nothing has been published yet. A robot with nothing published is still listed — \"not configured yet\" is a real state, and the caller is entitled to it — and its datasheet answers an empty exposure list."
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"required": [
|
|
68
|
+
"id",
|
|
69
|
+
"name",
|
|
70
|
+
"created_at",
|
|
71
|
+
"bridge_state",
|
|
72
|
+
"published_version"
|
|
73
|
+
],
|
|
74
|
+
"additionalProperties": false
|
|
75
|
+
},
|
|
76
|
+
"description": "Every robot the caller reaches, in name order with the id as the tiebreak. An app user reaches the robots their app attaches on which their role grants at least one slug or capability; a server key reaches every robot its app attaches; a developer reaches every robot of the organisation."
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"required": [
|
|
80
|
+
"robots"
|
|
81
|
+
],
|
|
82
|
+
"additionalProperties": false
|
|
83
|
+
}
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
]
|
|
119
119
|
},
|
|
120
120
|
"description": {
|
|
121
|
-
"description": "Prose about what this value is, for whoever meets it in the console
|
|
121
|
+
"description": "Prose about what this value is, for whoever meets it in the console. It changes nothing the robot does, so a publish that touches only it pushes no configuration at all — but it is carried verbatim into `robot_describe`, where a model that has never seen this robot reads it. The datapoint is offered whenever the role grants it; without one it is offered with `description: null` and the model has less to go on, as for actions, services, publishers and cameras. Omission is the only way to say nothing; an empty string is refused, here and on all five.",
|
|
122
122
|
"examples": [
|
|
123
123
|
"What this value is, for whoever meets it in the console."
|
|
124
124
|
],
|