@fleetless/contracts 1.2.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/artifacts/constants.json +30 -4
  3. package/artifacts/openapi.json +580 -49
  4. package/artifacts/routes.json +93 -2
  5. package/artifacts/schema/apply-error.schema.json +2 -1
  6. package/artifacts/schema/asset-list-response.schema.json +77 -12
  7. package/artifacts/schema/asset-sync-status.schema.json +35 -8
  8. package/artifacts/schema/asset.schema.json +2 -3
  9. package/artifacts/schema/assets-clear-response.schema.json +23 -0
  10. package/artifacts/schema/bridge-asset-progress.schema.json +14 -8
  11. package/artifacts/schema/bridge-config-applied.schema.json +2 -1
  12. package/artifacts/schema/bridge-link-mode.schema.json +36 -0
  13. package/artifacts/schema/bridge-state.schema.json +6 -1
  14. package/artifacts/schema/client-robot-list-item.schema.json +6 -1
  15. package/artifacts/schema/client-robot-list-response.schema.json +6 -1
  16. package/artifacts/schema/cloud-config.schema.json +90 -5
  17. package/artifacts/schema/cloud-hello-ok.schema.json +45 -0
  18. package/artifacts/schema/cloud-ping.schema.json +27 -1
  19. package/artifacts/schema/config-draft-response.schema.json +90 -5
  20. package/artifacts/schema/config-state.schema.json +2 -1
  21. package/artifacts/schema/config-version-response.schema.json +90 -5
  22. package/artifacts/schema/datapoint-config.schema.json +5 -0
  23. package/artifacts/schema/datapoint-frame.schema.json +4 -0
  24. package/artifacts/schema/datapoint-list-response.schema.json +2 -2
  25. package/artifacts/schema/joint-state-put-request.schema.json +23 -0
  26. package/artifacts/schema/joint-state-put-response.schema.json +24 -0
  27. package/artifacts/schema/org-quota-usage-counts.schema.json +0 -5
  28. package/artifacts/schema/org-quota-usage.schema.json +1 -12
  29. package/artifacts/schema/org-quotas.schema.json +1 -7
  30. package/artifacts/schema/robot-config-doc.schema.json +90 -5
  31. package/artifacts/schema/robot-deletion-summary.schema.json +2 -1
  32. package/artifacts/schema/robot-detail-response.schema.json +63 -2
  33. package/artifacts/schema/robot-list-item.schema.json +15 -1
  34. package/artifacts/schema/robot-list-response.schema.json +15 -1
  35. package/artifacts/schema/robot-token-rotate-response.schema.json +15 -0
  36. package/artifacts/schema-outgoing/bridge-asset-progress.schema.json +14 -8
  37. package/artifacts/schema-outgoing/bridge-config-applied.schema.json +2 -1
  38. package/artifacts/schema-outgoing/bridge-link-mode.schema.json +37 -0
  39. package/artifacts/schema-outgoing/datapoint-frame.schema.json +4 -0
  40. package/dist/assets.d.ts +85 -50
  41. package/dist/assets.js +152 -62
  42. package/dist/audit.d.ts +1 -1
  43. package/dist/audit.js +1 -1
  44. package/dist/client-robots.d.ts +2 -0
  45. package/dist/common.d.ts +10 -0
  46. package/dist/common.js +16 -1
  47. package/dist/config.d.ts +69 -1
  48. package/dist/config.js +86 -6
  49. package/dist/errors.d.ts +1 -1
  50. package/dist/errors.js +1 -8
  51. package/dist/index.d.ts +8 -8
  52. package/dist/index.js +4 -4
  53. package/dist/protocol.d.ts +150 -71
  54. package/dist/protocol.js +144 -87
  55. package/dist/rest.d.ts +137 -35
  56. package/dist/rest.js +98 -66
  57. package/dist/routes.js +57 -8
  58. package/package.json +1 -1
  59. package/artifacts/schema/bridge-pressure.schema.json +0 -292
package/dist/routes.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  import { appListResponse, createAppRequest, createServerKeyResponse, app as appSchema, role, roleListResponse, rolePermissions, serverKeyListResponse, updateAppRequest, } from './apps.js';
3
3
  import { alertListResponse, orgAlertsQuery, orgFiringAlertsResponse } from './alerts.js';
4
- import { asset, assetListResponse, assetSyncRequest, assetSyncResponse, assetSyncStatus, missingAssetQuery } from './assets.js';
4
+ import { asset, assetListResponse, assetsClearResponse, assetSyncRequest, assetSyncResponse, assetSyncStatus, missingAssetQuery } from './assets.js';
5
5
  import { auditListResponse, auditQuery } from './audit.js';
6
6
  import { CLIENT_OIDC_CALLBACK_PATH, clientAcceptInvitationRequest, clientIdentity, clientLoginRequest, clientLogoutRequest, clientMcpInteraction, clientMcpInteractionDecisionResponse, clientOidcCallbackQuery, clientOidcExchangeRequest, clientOidcStartQuery, clientPasswordResetConfirmRequest, clientPasswordResetRequest, clientProviderListQuery, clientProviderListResponse, clientRefreshRequest, clientRegisterRequest, clientResendVerificationRequest, clientVerifyEmailRequest, mcpConsentGrantListResponse, } from './client-auth.js';
7
7
  import { clientRobotListResponse } from './client-robots.js';
@@ -10,7 +10,7 @@ import { acceptTeamInviteRequest, authMeResponse, createTeamInviteRequest, fleet
10
10
  import { jobRunListResponse, jobRunQuery, jobRunSummary, jobRunSummaryQuery } from './jobs.js';
11
11
  import { MCP_APP_PATHS, mcpRobotDatasheet, mcpRolePreviewResponse } from './mcp.js';
12
12
  import { authorizationServerMetadata, dynamicClientRegistrationRequest, dynamicClientRegistrationResponse, oauthAuthorizeQuery, oauthRedirectResponse, oauthTokenRequest, oauthTokenResponse, protectedResourceMetadata, } from './oauth.js';
13
- import { cameraListResponse, cancelRequest, configDraftResponse, configVersionResponse, configVersionsResponse, createRobotRequest, createRobotResponse, datapointListResponse, datapointValue, exposureListResponse, fetchTypesRequest, fetchTypesResponse, historyQuery, historyResponse, introspectionResponse, invokeOrServiceResponse, invokeRequest, jobResponse, liveSessionResponse, orgHealthQuery, orgLatencyQuery, orgLatencyResponse, orgQuotaUsage, orgUsageQuery, orgUsageResponse, patchRobotRequest, patchRobotResponse, publishConfigResponse, publishRequest, putConfigDraftRequest, putRobotDetailsRequest, putRobotDetailsResponse, releaseLiveQuery, renameSlugRequest, renameSlugResponse, robotDeleteQuery, resourceHealthListResponse, robotDeletionSummary, robotDetailResponse, robotJobsResponse, robotListResponse, slugUsageResponse, snapshotMetaResponse, typesResponse, } from './rest.js';
13
+ import { cameraListResponse, cancelRequest, configDraftResponse, configVersionResponse, configVersionsResponse, createRobotRequest, createRobotResponse, datapointListResponse, datapointValue, exposureListResponse, fetchTypesRequest, fetchTypesResponse, historyQuery, historyResponse, introspectionResponse, invokeOrServiceResponse, invokeRequest, jobResponse, liveSessionResponse, orgHealthQuery, orgLatencyQuery, orgLatencyResponse, orgQuotaUsage, orgUsageQuery, orgUsageResponse, patchRobotRequest, patchRobotResponse, publishConfigResponse, publishRequest, putConfigDraftRequest, putRobotDetailsRequest, putRobotDetailsResponse, robotTokenRotateResponse, jointStatePutRequest, jointStatePutResponse, releaseLiveQuery, renameSlugRequest, renameSlugResponse, robotDeleteQuery, resourceHealthListResponse, robotDeletionSummary, robotDetailResponse, robotJobsResponse, robotListResponse, slugUsageResponse, snapshotMetaResponse, typesResponse, } from './rest.js';
14
14
  export const ROUTE_SECTIONS = [
15
15
  { id: 'health', title: 'Health' },
16
16
  { id: 'developer-auth', title: 'Developer auth' },
@@ -1664,6 +1664,40 @@ export const ROUTES = [
1664
1664
  'and the event carries no `details`, because the one interesting value here is the token. `max_robots` is checked before anything is ' +
1665
1665
  'created, which is only safe because robot deletion exists.',
1666
1666
  },
1667
+ {
1668
+ method: 'POST', path: '/api/robots/:id/token/rotate', section: 'robots',
1669
+ summary: 'Mints a new bridge token for the robot and invalidates the old one.',
1670
+ audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: true, status: 201,
1671
+ params: [{ name: 'id', description: 'The robot\'s uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`.' }],
1672
+ query: null, request: null, response: robotTokenRotateResponse,
1673
+ errors: [...DEVELOPER_GUARD, 'tier_required', 'invalid_uuid', 'not_found'], transport: 'http',
1674
+ 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 ' +
1675
+ '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 ' +
1676
+ 'stores a hash — so a caller who loses it rotates again. Audited as `robot.token_rotated`, with no `details`: the one interesting value ' +
1677
+ '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 ' +
1678
+ 'the cloud closes that socket with `CLOSE_TOKEN_ROTATED` rather than leaving a bridge speaking on a credential nothing would accept ' +
1679
+ '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 ' +
1680
+ 'the same way. **The robot is offline until somebody puts the new token on it** — this is a deliberate interruption, not a background ' +
1681
+ 'rekey, and a fleet cannot be rotated without a visit to each robot.',
1682
+ },
1683
+ {
1684
+ method: 'PUT', path: '/api/robots/:id/urdf/joint-state', section: 'robots',
1685
+ summary: 'Chooses the datapoint whose joint positions move the robot\'s URDF, or clears it.',
1686
+ audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 200,
1687
+ params: [{ name: 'id', description: 'The robot\'s uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`.' }],
1688
+ query: null, request: jointStatePutRequest, response: jointStatePutResponse,
1689
+ errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'not_found', 'validation_error'], transport: 'http',
1690
+ notes: '**What qualifies**: a datapoint of the **published** configuration whose ROS type is `sensor_msgs/msg/JointState` and which carries no ' +
1691
+ '`field` — the whole message, because positions and names arrive together and a single extracted field is half of a pose. Anything else ' +
1692
+ 'is a `validation_error` naming that rule rather than a stored mapping that renders a battery reading as a robot. `{ "slug": null }` ' +
1693
+ 'clears it, which is why the field is required and nullable rather than optional. \n\n**The mapping cannot outlive what it points at.** ' +
1694
+ 'Every successful publish re-checks it against the new document and clears it when it no longer qualifies, recording ' +
1695
+ '`robot.joint_state_cleared` with the version that did it; a slug rename rewrites it like every other reference the editor already ' +
1696
+ 'rewrites; deleting the robot takes it along. Every write through this route — a slug or `null` — is on the record too, as ' +
1697
+ '`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 ' +
1698
+ 'value reads back on `GET /api/robots/:id/assets` as `joint_state_slug`, so a renderer fetches the URDF, the meshes and the mapping ' +
1699
+ 'from one place.',
1700
+ },
1667
1701
  {
1668
1702
  method: 'GET', path: '/api/robots', section: 'robots',
1669
1703
  summary: "Lists the org's robots with their connection state and exposure counts.",
@@ -2233,6 +2267,20 @@ export const ROUTES = [
2233
2267
  notes: 'Developer sessions only, like starting a sync: the guard admits three caller kinds and the handler answers `401 unauthorized` to the ' +
2234
2268
  'other two. A sync belonging to another robot reads exactly like one that never existed, which is why the robot is resolved first.',
2235
2269
  },
2270
+ {
2271
+ method: 'DELETE', path: '/api/robots/:id/assets', section: 'assets',
2272
+ summary: "Empties a robot's asset store: every URDF, mesh and texture, gone at once.",
2273
+ audience: 'client', auth: 'developer_or_client', rateLimited: false, ownerTier: true, status: 200,
2274
+ params: [{ name: 'id', description: 'The robot\'s uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`.' }],
2275
+ query: null, request: null, response: assetsClearResponse,
2276
+ errors: [...CLIENT_GUARD, 'tier_required', 'invalid_uuid', 'not_found', 'busy'], transport: 'http',
2277
+ 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 ' +
2278
+ 'is exempt from the gate, reconcile after a sync already frees what the new URDF stopped referencing, and this route lets an Owner clear ' +
2279
+ 'the robot outright. Owner tier, unconditionally, like starting a sync. Removes every asset of the robot and resets its store to `0`; ' +
2280
+ "the next sync fills it again. It does not touch the bridge's availability report — `urdf_available` still answers from the connected " +
2281
+ 'robot, unrelated to what this cloud happens to have stored. A clear while a sync is running is `409 busy` naming that sync\'s ' +
2282
+ 'details, the same refusal starting a second sync gets, because deleting under a running upload would leave the store counter wrong.',
2283
+ },
2236
2284
  /* ------------------------------------------------ org (quotas and fleet reads) */
2237
2285
  {
2238
2286
  method: 'GET', path: '/api/org/quotas', section: 'org',
@@ -2306,15 +2354,16 @@ export const ROUTES = [
2306
2354
  summary: 'Takes one asset file from a robot during a sync.',
2307
2355
  audience: 'internal', auth: 'robot_upload', rateLimited: false, ownerTier: false, status: 201,
2308
2356
  params: [], query: null, request: null, response: asset,
2309
- errors: ['unauthorized', 'rate_limited', 'asset_too_large', 'validation_error', 'not_found', 'quota_exceeded', 'bad_request'], transport: 'http',
2357
+ errors: ['unauthorized', 'rate_limited', 'validation_error', 'not_found', 'quota_exceeded', 'bad_request'], transport: 'http',
2310
2358
  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 ' +
2311
2359
  'and its announced size — rides in the `x-fleetless-asset-*` headers `ASSET_UPLOAD_HEADERS` names. The credential is a short-lived ' +
2312
2360
  'upload token minted by `POST /api/robots/:id/assets/sync`, verified in a `preParsing` hook so a refusal precedes the work rather than ' +
2313
- 'following it: a `preHandler` would already have buffered the whole file. The announced size is refused there too, before a single byte ' +
2314
- 'is read — it is an announcement and not a proof, so it only ever rejects early and never accepts early, and a body that lies small is ' +
2315
- 'still caught by the real length check. Past both, the server\'s own body limit answers a bare `413 bad_request` with neither ceiling nor ' +
2316
- 'size in it. Rate limited per robot inside that same hook, which is why `rateLimited` is `false`: there is no rate-limiting preHandler ' +
2317
- 'registered on this route.',
2361
+ 'following it: a `preHandler` would already have buffered the whole file. **Nothing is refused for its own size** — the robot\'s asset ' +
2362
+ '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 ' +
2363
+ '`409 quota_exceeded` carrying `store_bytes`, `used_bytes` and `size_bytes`, while the sync carries on with the next file. Past that, ' +
2364
+ '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 ' +
2365
+ 'hook, which is why `rateLimited` is `false`: there is no rate-limiting preHandler registered on this route. The URDF itself is never ' +
2366
+ 'refused for the store; only meshes and textures are charged against it.',
2318
2367
  },
2319
2368
  /* ------------------------------------ realtime and bridge transports */
2320
2369
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetless/contracts",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "Fleetless wire contracts: the bridge-cloud protocol, the REST API schemas and the error codes, as zod schemas with generated JSON Schema and OpenAPI artifacts.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dehne Robotik GmbH",
@@ -1,292 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "type": "object",
4
- "properties": {
5
- "link": {
6
- "type": "object",
7
- "properties": {
8
- "rate_bps": {
9
- "anyOf": [
10
- {
11
- "type": "number",
12
- "minimum": 0
13
- },
14
- {
15
- "type": "null"
16
- }
17
- ]
18
- },
19
- "snapshot_max_bytes": {
20
- "type": "integer",
21
- "minimum": 0,
22
- "maximum": 9007199254740991
23
- }
24
- },
25
- "required": [
26
- "rate_bps",
27
- "snapshot_max_bytes"
28
- ]
29
- },
30
- "tiers": {
31
- "type": "object",
32
- "properties": {
33
- "0": {
34
- "type": "object",
35
- "properties": {
36
- "sent": {
37
- "type": "integer",
38
- "minimum": 0,
39
- "maximum": 9007199254740991
40
- },
41
- "bytes": {
42
- "type": "integer",
43
- "minimum": 0,
44
- "maximum": 9007199254740991
45
- },
46
- "drops": {
47
- "type": "integer",
48
- "minimum": 0,
49
- "maximum": 9007199254740991
50
- },
51
- "high_water": {
52
- "type": "integer",
53
- "minimum": 0,
54
- "maximum": 9007199254740991
55
- }
56
- },
57
- "required": [
58
- "sent",
59
- "bytes",
60
- "drops",
61
- "high_water"
62
- ]
63
- },
64
- "1": {
65
- "type": "object",
66
- "properties": {
67
- "sent": {
68
- "type": "integer",
69
- "minimum": 0,
70
- "maximum": 9007199254740991
71
- },
72
- "bytes": {
73
- "type": "integer",
74
- "minimum": 0,
75
- "maximum": 9007199254740991
76
- },
77
- "drops": {
78
- "type": "integer",
79
- "minimum": 0,
80
- "maximum": 9007199254740991
81
- },
82
- "high_water": {
83
- "type": "integer",
84
- "minimum": 0,
85
- "maximum": 9007199254740991
86
- }
87
- },
88
- "required": [
89
- "sent",
90
- "bytes",
91
- "drops",
92
- "high_water"
93
- ]
94
- },
95
- "2": {
96
- "type": "object",
97
- "properties": {
98
- "sent": {
99
- "type": "integer",
100
- "minimum": 0,
101
- "maximum": 9007199254740991
102
- },
103
- "bytes": {
104
- "type": "integer",
105
- "minimum": 0,
106
- "maximum": 9007199254740991
107
- },
108
- "drops": {
109
- "type": "integer",
110
- "minimum": 0,
111
- "maximum": 9007199254740991
112
- },
113
- "high_water": {
114
- "type": "integer",
115
- "minimum": 0,
116
- "maximum": 9007199254740991
117
- }
118
- },
119
- "required": [
120
- "sent",
121
- "bytes",
122
- "drops",
123
- "high_water"
124
- ]
125
- },
126
- "3": {
127
- "type": "object",
128
- "properties": {
129
- "sent": {
130
- "type": "integer",
131
- "minimum": 0,
132
- "maximum": 9007199254740991
133
- },
134
- "bytes": {
135
- "type": "integer",
136
- "minimum": 0,
137
- "maximum": 9007199254740991
138
- },
139
- "drops": {
140
- "type": "integer",
141
- "minimum": 0,
142
- "maximum": 9007199254740991
143
- },
144
- "high_water": {
145
- "type": "integer",
146
- "minimum": 0,
147
- "maximum": 9007199254740991
148
- }
149
- },
150
- "required": [
151
- "sent",
152
- "bytes",
153
- "drops",
154
- "high_water"
155
- ]
156
- },
157
- "4": {
158
- "type": "object",
159
- "properties": {
160
- "sent": {
161
- "type": "integer",
162
- "minimum": 0,
163
- "maximum": 9007199254740991
164
- },
165
- "bytes": {
166
- "type": "integer",
167
- "minimum": 0,
168
- "maximum": 9007199254740991
169
- },
170
- "drops": {
171
- "type": "integer",
172
- "minimum": 0,
173
- "maximum": 9007199254740991
174
- },
175
- "high_water": {
176
- "type": "integer",
177
- "minimum": 0,
178
- "maximum": 9007199254740991
179
- }
180
- },
181
- "required": [
182
- "sent",
183
- "bytes",
184
- "drops",
185
- "high_water"
186
- ]
187
- },
188
- "5": {
189
- "type": "object",
190
- "properties": {
191
- "sent": {
192
- "type": "integer",
193
- "minimum": 0,
194
- "maximum": 9007199254740991
195
- },
196
- "bytes": {
197
- "type": "integer",
198
- "minimum": 0,
199
- "maximum": 9007199254740991
200
- },
201
- "drops": {
202
- "type": "integer",
203
- "minimum": 0,
204
- "maximum": 9007199254740991
205
- },
206
- "high_water": {
207
- "type": "integer",
208
- "minimum": 0,
209
- "maximum": 9007199254740991
210
- }
211
- },
212
- "required": [
213
- "sent",
214
- "bytes",
215
- "drops",
216
- "high_water"
217
- ]
218
- }
219
- },
220
- "additionalProperties": false
221
- },
222
- "video": {
223
- "type": "object",
224
- "properties": {
225
- "active_streams": {
226
- "type": "integer",
227
- "minimum": 0,
228
- "maximum": 9007199254740991
229
- },
230
- "bitrate_sum_kbps": {
231
- "type": "integer",
232
- "minimum": 0,
233
- "maximum": 9007199254740991
234
- },
235
- "uplink_kbps": {
236
- "anyOf": [
237
- {
238
- "type": "integer",
239
- "minimum": 0,
240
- "maximum": 9007199254740991
241
- },
242
- {
243
- "type": "null"
244
- }
245
- ]
246
- },
247
- "override_kbps": {
248
- "anyOf": [
249
- {
250
- "type": "integer",
251
- "minimum": 0,
252
- "maximum": 9007199254740991
253
- },
254
- {
255
- "type": "null"
256
- }
257
- ]
258
- },
259
- "video_budget_kbps": {
260
- "anyOf": [
261
- {
262
- "type": "integer",
263
- "minimum": 0,
264
- "maximum": 9007199254740991
265
- },
266
- {
267
- "type": "null"
268
- }
269
- ]
270
- },
271
- "reserve_kbps": {
272
- "type": "integer",
273
- "minimum": 0,
274
- "maximum": 9007199254740991
275
- }
276
- },
277
- "required": [
278
- "active_streams",
279
- "bitrate_sum_kbps",
280
- "uplink_kbps",
281
- "override_kbps",
282
- "video_budget_kbps",
283
- "reserve_kbps"
284
- ]
285
- }
286
- },
287
- "required": [
288
- "link",
289
- "tiers",
290
- "video"
291
- ]
292
- }