@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
@@ -3052,6 +3052,66 @@
3052
3052
  "transport": "http",
3053
3053
  "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
3054
  },
3055
+ {
3056
+ "method": "POST",
3057
+ "path": "/api/robots/:id/token/rotate",
3058
+ "section": "robots",
3059
+ "summary": "Mints a new bridge token for the robot and invalidates the old one.",
3060
+ "audience": "developer",
3061
+ "auth": "developer",
3062
+ "rateLimited": false,
3063
+ "ownerTier": true,
3064
+ "status": 201,
3065
+ "params": [
3066
+ {
3067
+ "name": "id",
3068
+ "description": "The robot's uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`."
3069
+ }
3070
+ ],
3071
+ "query": null,
3072
+ "request": null,
3073
+ "response": "robot-token-rotate-response",
3074
+ "errors": [
3075
+ "unauthorized",
3076
+ "token_expired",
3077
+ "token_revoked",
3078
+ "tier_required",
3079
+ "invalid_uuid",
3080
+ "not_found"
3081
+ ],
3082
+ "transport": "http",
3083
+ "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."
3084
+ },
3085
+ {
3086
+ "method": "PUT",
3087
+ "path": "/api/robots/:id/urdf/joint-state",
3088
+ "section": "robots",
3089
+ "summary": "Chooses the datapoint whose joint positions move the robot's URDF, or clears it.",
3090
+ "audience": "developer",
3091
+ "auth": "developer",
3092
+ "rateLimited": false,
3093
+ "ownerTier": false,
3094
+ "status": 200,
3095
+ "params": [
3096
+ {
3097
+ "name": "id",
3098
+ "description": "The robot's uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`."
3099
+ }
3100
+ ],
3101
+ "query": null,
3102
+ "request": "joint-state-put-request",
3103
+ "response": "joint-state-put-response",
3104
+ "errors": [
3105
+ "unauthorized",
3106
+ "token_expired",
3107
+ "token_revoked",
3108
+ "invalid_uuid",
3109
+ "not_found",
3110
+ "validation_error"
3111
+ ],
3112
+ "transport": "http",
3113
+ "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."
3114
+ },
3055
3115
  {
3056
3116
  "method": "GET",
3057
3117
  "path": "/api/robots",
@@ -4467,6 +4527,38 @@
4467
4527
  "transport": "http",
4468
4528
  "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
4529
  },
4530
+ {
4531
+ "method": "DELETE",
4532
+ "path": "/api/robots/:id/assets",
4533
+ "section": "assets",
4534
+ "summary": "Empties a robot's asset store: every URDF, mesh and texture, gone at once.",
4535
+ "audience": "client",
4536
+ "auth": "developer_or_client",
4537
+ "rateLimited": false,
4538
+ "ownerTier": true,
4539
+ "status": 200,
4540
+ "params": [
4541
+ {
4542
+ "name": "id",
4543
+ "description": "The robot's uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`."
4544
+ }
4545
+ ],
4546
+ "query": null,
4547
+ "request": null,
4548
+ "response": "assets-clear-response",
4549
+ "errors": [
4550
+ "unauthorized",
4551
+ "token_expired",
4552
+ "token_revoked",
4553
+ "forbidden",
4554
+ "tier_required",
4555
+ "invalid_uuid",
4556
+ "not_found",
4557
+ "busy"
4558
+ ],
4559
+ "transport": "http",
4560
+ "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."
4561
+ },
4470
4562
  {
4471
4563
  "method": "GET",
4472
4564
  "path": "/api/org/quotas",
@@ -4622,14 +4714,13 @@
4622
4714
  "errors": [
4623
4715
  "unauthorized",
4624
4716
  "rate_limited",
4625
- "asset_too_large",
4626
4717
  "validation_error",
4627
4718
  "not_found",
4628
4719
  "quota_exceeded",
4629
4720
  "bad_request"
4630
4721
  ],
4631
4722
  "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. The announced size is refused there too, before a single byte 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 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 size 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."
4723
+ "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
4724
  },
4634
4725
  {
4635
4726
  "method": "GET",
@@ -12,7 +12,8 @@
12
12
  "action",
13
13
  "service",
14
14
  "publisher",
15
- "camera"
15
+ "camera",
16
+ "low_bandwidth"
16
17
  ]
17
18
  },
18
19
  "code": {
@@ -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, or `other`. A renderer decides from this alone, before fetching anything, what to pre-fetch."
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 a producer-side ceiling was hit, and `too_large` means it exceeds the upload limit and carries both numbers in `details`."
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 two numbers behind a `too_large` failure, and absent for every other kind — a forced `null` on every `unresolvable` entry buys nothing. The pairing is enforced, not merely described.",
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
- "limit_bytes": {
140
+ "store_bytes": {
143
141
  "type": "integer",
144
142
  "exclusiveMinimum": 0,
145
143
  "maximum": 9007199254740991,
146
- "description": "The upload ceiling, in bytes."
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": "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."
156
+ "description": "The refused upload, in bytes."
153
157
  }
154
158
  },
155
159
  "required": [
156
- "limit_bytes",
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 a producer-side ceiling was hit, and `too_large` means it exceeds the upload limit and carries both numbers in `details`."
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 two numbers behind a `too_large` failure, and absent for every other kind — a forced `null` on every `unresolvable` entry buys nothing. The pairing is enforced, not merely described.",
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
- "limit_bytes": {
65
+ "store_bytes": {
67
66
  "type": "integer",
68
67
  "exclusiveMinimum": 0,
69
68
  "maximum": 9007199254740991,
70
- "description": "The upload ceiling, in bytes."
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": "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."
81
+ "description": "The refused upload, in bytes."
77
82
  }
78
83
  },
79
84
  "required": [
80
- "limit_bytes",
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, or `other`. A renderer decides from this alone, before fetching anything, what to pre-fetch."
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
+ }
@@ -38,32 +38,38 @@
38
38
  "enum": [
39
39
  "unresolvable",
40
40
  "upload_failed",
41
- "refused",
42
- "too_large"
41
+ "refused"
43
42
  ],
44
- "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 a producer-side ceiling was hit, and `too_large` means it exceeds the upload limit and carries both numbers in `details`."
43
+ "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."
45
44
  },
46
45
  "details": {
47
- "description": "The two numbers behind a `too_large` failure, and absent for every other kind — a forced `null` on every `unresolvable` entry buys nothing. The pairing is enforced, not merely described.",
46
+ "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.",
48
47
  "anyOf": [
49
48
  {
50
49
  "type": "object",
51
50
  "properties": {
52
- "limit_bytes": {
51
+ "store_bytes": {
53
52
  "type": "integer",
54
53
  "exclusiveMinimum": 0,
55
54
  "maximum": 9007199254740991,
56
- "description": "The upload ceiling, in bytes."
55
+ "description": "The robot's store, in bytes."
56
+ },
57
+ "used_bytes": {
58
+ "type": "integer",
59
+ "minimum": 0,
60
+ "maximum": 9007199254740991,
61
+ "description": "Bytes the robot's assets occupy before this upload."
57
62
  },
58
63
  "size_bytes": {
59
64
  "type": "integer",
60
65
  "exclusiveMinimum": 0,
61
66
  "maximum": 9007199254740991,
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."
67
+ "description": "The refused upload, in bytes."
63
68
  }
64
69
  },
65
70
  "required": [
66
- "limit_bytes",
71
+ "store_bytes",
72
+ "used_bytes",
67
73
  "size_bytes"
68
74
  ]
69
75
  },
@@ -29,7 +29,8 @@
29
29
  "action",
30
30
  "service",
31
31
  "publisher",
32
- "camera"
32
+ "camera",
33
+ "low_bandwidth"
33
34
  ]
34
35
  },
35
36
  "code": {
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "type": {
6
+ "type": "string",
7
+ "const": "link_mode"
8
+ },
9
+ "low_bandwidth": {
10
+ "type": "boolean",
11
+ "description": "Whether the mode is active after this transition."
12
+ },
13
+ "reason": {
14
+ "type": "string",
15
+ "enum": [
16
+ "lag",
17
+ "dwell",
18
+ "forced",
19
+ "recovered"
20
+ ],
21
+ "description": "`lag`: the cloud-measured lag crossed the threshold; `dwell`: the bridge-measured queue dwell did; `forced`: `mode: on` or `off`; `recovered`: both measures stayed at or below the exit threshold."
22
+ },
23
+ "at_ms": {
24
+ "type": "integer",
25
+ "minimum": 0,
26
+ "maximum": 9007199254740991,
27
+ "description": "Bridge time of the transition, epoch milliseconds."
28
+ }
29
+ },
30
+ "required": [
31
+ "type",
32
+ "low_bandwidth",
33
+ "reason",
34
+ "at_ms"
35
+ ]
36
+ }
@@ -15,10 +15,15 @@
15
15
  "type": "null"
16
16
  }
17
17
  ]
18
+ },
19
+ "low_bandwidth": {
20
+ "type": "boolean",
21
+ "description": "Whether the bridge is in its low-bandwidth mode: datapoints capped, cameras reduced or stopped. Bridge-reported."
18
22
  }
19
23
  },
20
24
  "required": [
21
25
  "online",
22
- "latency_ms"
26
+ "latency_ms",
27
+ "low_bandwidth"
23
28
  ]
24
29
  }
@@ -36,11 +36,16 @@
36
36
  "type": "null"
37
37
  }
38
38
  ]
39
+ },
40
+ "low_bandwidth": {
41
+ "type": "boolean",
42
+ "description": "Whether the bridge is in its low-bandwidth mode: datapoints capped, cameras reduced or stopped. Bridge-reported."
39
43
  }
40
44
  },
41
45
  "required": [
42
46
  "online",
43
- "latency_ms"
47
+ "latency_ms",
48
+ "low_bandwidth"
44
49
  ],
45
50
  "additionalProperties": false,
46
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."
@@ -41,11 +41,16 @@
41
41
  "type": "null"
42
42
  }
43
43
  ]
44
+ },
45
+ "low_bandwidth": {
46
+ "type": "boolean",
47
+ "description": "Whether the bridge is in its low-bandwidth mode: datapoints capped, cameras reduced or stopped. Bridge-reported."
44
48
  }
45
49
  },
46
50
  "required": [
47
51
  "online",
48
- "latency_ms"
52
+ "latency_ms",
53
+ "low_bandwidth"
49
54
  ],
50
55
  "additionalProperties": false,
51
56
  "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."