@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/rest.d.ts CHANGED
@@ -37,6 +37,39 @@ export declare const createRobotResponse: z.ZodObject<{
37
37
  token: z.ZodString;
38
38
  }, z.core.$strip>;
39
39
  export type CreateRobotResponse = z.infer<typeof createRobotResponse>;
40
+ /**
41
+ * What a rotation hands back: the new token, once.
42
+ *
43
+ * The same shape as the creation response minus the robot, because nothing
44
+ * about the robot changed — only its credential. `createRobotResponse`'s own
45
+ * rule applies unchanged: the cloud stores a hash, so this is the only moment
46
+ * the raw token exists outside the caller's hands.
47
+ */
48
+ export declare const robotTokenRotateResponse: z.ZodObject<{
49
+ token: z.ZodString;
50
+ }, z.core.$strip>;
51
+ export type RobotTokenRotateResponse = z.infer<typeof robotTokenRotateResponse>;
52
+ /**
53
+ * Which datapoint drives the joints of this robot's URDF, or none.
54
+ *
55
+ * `null` is the clearing value, which is why `slug` is required rather than
56
+ * optional: an absent field and a cleared mapping would be the same request
57
+ * and mean different things, and the one a client sends by accident is the
58
+ * first.
59
+ *
60
+ * The cloud refuses a slug that is not a whole-message
61
+ * `sensor_msgs/msg/JointState` datapoint of the **published** document — a
62
+ * mapping that may point anywhere is a viewer animating a battery reading.
63
+ */
64
+ export declare const jointStatePutRequest: z.ZodObject<{
65
+ slug: z.ZodNullable<z.ZodString>;
66
+ }, z.core.$strip>;
67
+ export type JointStatePutRequest = z.infer<typeof jointStatePutRequest>;
68
+ /** The mapping as it now stands — the same field `GET /api/robots/:id/assets` reports. */
69
+ export declare const jointStatePutResponse: z.ZodObject<{
70
+ joint_state_slug: z.ZodNullable<z.ZodString>;
71
+ }, z.core.$strip>;
72
+ export type JointStatePutResponse = z.infer<typeof jointStatePutResponse>;
40
73
  /**
41
74
  * How many things a robot exposes, per kind.
42
75
  *
@@ -46,18 +79,17 @@ export type CreateRobotResponse = z.infer<typeof createRobotResponse>;
46
79
  *
47
80
  * **Counted from the published configuration, and excluding the built-ins.**
48
81
  * `GET /api/robots/:id/exposures` answers *which* slugs and prepends the
49
- * three built-in datapoints — `bridge_state`, `robot_details` and
50
- * `bridge_pressure` — as `builtin: true`; this answers *how many* and counts
51
- * only what somebody configured. So a robot with an empty published config
52
- * reports `datapoints: 0` here and three entries there. That is intentional,
53
- * and it is written on both sides so the disagreement is never mistaken for a
54
- * bug.
55
- *
56
- * The number is "three" and not "two" as of `bridge_pressure`; the cloud
57
- * builds that prefix from `PLANE_BUILTIN_DATAPOINTS` rather than a literal,
58
- * so a further built-in moves this count again. Read the count off that set,
59
- * not off this sentence, before filing the bug this comment exists to
60
- * prevent.
82
+ * built-in datapoints — `bridge_state` and `robot_details` — as
83
+ * `builtin: true`; this answers *how many* and counts only what somebody
84
+ * configured. So a robot with an empty published config reports
85
+ * `datapoints: 0` here and two entries there. That is intentional, and it is
86
+ * written on both sides so the disagreement is never mistaken for a bug.
87
+ *
88
+ * The number was "three" while `bridge_pressure` existed and is "two" since
89
+ * protocol 3 dropped it; the cloud builds that prefix from its own built-in
90
+ * set rather than a literal, so the next built-in moves this count again.
91
+ * Read the count off that set, not off this sentence, before filing the bug
92
+ * this comment exists to prevent.
61
93
  */
62
94
  export declare const exposureCounts: z.ZodObject<{
63
95
  datapoints: z.ZodNumber;
@@ -67,11 +99,24 @@ export declare const exposureCounts: z.ZodObject<{
67
99
  cameras: z.ZodNumber;
68
100
  }, z.core.$strip>;
69
101
  export type ExposureCounts = z.infer<typeof exposureCounts>;
102
+ /**
103
+ * Where a robot's bridge stands against the protocol window.
104
+ * `refused`: its last hello was refused for its version — it is offline
105
+ * until upgraded. Computed by the cloud from `protocol_version` and
106
+ * `last_hello_error`, never stored.
107
+ */
108
+ export declare const protocolStatusValue: z.ZodEnum<{
109
+ deprecated: "deprecated";
110
+ refused: "refused";
111
+ current: "current";
112
+ }>;
113
+ export type ProtocolStatusValue = z.infer<typeof protocolStatusValue>;
70
114
  /** A robot as listed, with its current built-in `bridge_state`. */
71
115
  export declare const robotListItem: z.ZodObject<{
72
116
  bridge_state: z.ZodObject<{
73
117
  online: z.ZodBoolean;
74
118
  latency_ms: z.ZodNullable<z.ZodNumber>;
119
+ low_bandwidth: z.ZodBoolean;
75
120
  }, z.core.$strip>;
76
121
  exposes: z.ZodObject<{
77
122
  datapoints: z.ZodNumber;
@@ -80,6 +125,11 @@ export declare const robotListItem: z.ZodObject<{
80
125
  publishers: z.ZodNumber;
81
126
  cameras: z.ZodNumber;
82
127
  }, z.core.$strip>;
128
+ protocol_status: z.ZodOptional<z.ZodEnum<{
129
+ deprecated: "deprecated";
130
+ refused: "refused";
131
+ current: "current";
132
+ }>>;
83
133
  id: z.ZodUUID;
84
134
  name: z.ZodString;
85
135
  created_at: z.ZodISODateTime;
@@ -90,6 +140,7 @@ export declare const robotListResponse: z.ZodObject<{
90
140
  bridge_state: z.ZodObject<{
91
141
  online: z.ZodBoolean;
92
142
  latency_ms: z.ZodNullable<z.ZodNumber>;
143
+ low_bandwidth: z.ZodBoolean;
93
144
  }, z.core.$strip>;
94
145
  exposes: z.ZodObject<{
95
146
  datapoints: z.ZodNumber;
@@ -98,6 +149,11 @@ export declare const robotListResponse: z.ZodObject<{
98
149
  publishers: z.ZodNumber;
99
150
  cameras: z.ZodNumber;
100
151
  }, z.core.$strip>;
152
+ protocol_status: z.ZodOptional<z.ZodEnum<{
153
+ deprecated: "deprecated";
154
+ refused: "refused";
155
+ current: "current";
156
+ }>>;
101
157
  id: z.ZodUUID;
102
158
  name: z.ZodString;
103
159
  created_at: z.ZodISODateTime;
@@ -122,6 +178,15 @@ export type DatapointValue = z.infer<typeof datapointValue>;
122
178
  */
123
179
  export declare const robotDetailResponse: z.ZodObject<{
124
180
  bridge_version: z.ZodNullable<z.ZodString>;
181
+ protocol_version: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
182
+ protocol: z.ZodOptional<z.ZodObject<{
183
+ status: z.ZodEnum<{
184
+ deprecated: "deprecated";
185
+ refused: "refused";
186
+ current: "current";
187
+ }>;
188
+ sunset_at: z.ZodNullable<z.ZodISODate>;
189
+ }, z.core.$strip>>;
125
190
  last_hello_error: z.ZodNullable<z.ZodObject<{
126
191
  code: z.ZodString;
127
192
  message: z.ZodString;
@@ -141,6 +206,7 @@ export declare const robotDetailResponse: z.ZodObject<{
141
206
  service: "service";
142
207
  publisher: "publisher";
143
208
  camera: "camera";
209
+ low_bandwidth: "low_bandwidth";
144
210
  }>;
145
211
  code: z.ZodString;
146
212
  message: z.ZodString;
@@ -150,6 +216,7 @@ export declare const robotDetailResponse: z.ZodObject<{
150
216
  bridge_state: z.ZodObject<{
151
217
  online: z.ZodBoolean;
152
218
  latency_ms: z.ZodNullable<z.ZodNumber>;
219
+ low_bandwidth: z.ZodBoolean;
153
220
  }, z.core.$strip>;
154
221
  exposes: z.ZodObject<{
155
222
  datapoints: z.ZodNumber;
@@ -158,6 +225,11 @@ export declare const robotDetailResponse: z.ZodObject<{
158
225
  publishers: z.ZodNumber;
159
226
  cameras: z.ZodNumber;
160
227
  }, z.core.$strip>;
228
+ protocol_status: z.ZodOptional<z.ZodEnum<{
229
+ deprecated: "deprecated";
230
+ refused: "refused";
231
+ current: "current";
232
+ }>>;
161
233
  id: z.ZodUUID;
162
234
  name: z.ZodString;
163
235
  created_at: z.ZodISODateTime;
@@ -203,6 +275,7 @@ export declare const configDraftResponse: z.ZodObject<{
203
275
  type: z.ZodString;
204
276
  field: z.ZodOptional<z.ZodString>;
205
277
  rate_throttle_hz: z.ZodOptional<z.ZodNumber>;
278
+ low_bandwidth: z.ZodOptional<z.ZodLiteral<"keep">>;
206
279
  description: z.ZodOptional<z.ZodString>;
207
280
  numeric: z.ZodOptional<z.ZodObject<{
208
281
  scale: z.ZodOptional<z.ZodNumber>;
@@ -369,6 +442,23 @@ export declare const configDraftResponse: z.ZodObject<{
369
442
  snapshot_interval_seconds: z.ZodNumber;
370
443
  description: z.ZodOptional<z.ZodString>;
371
444
  }, z.core.$strict>>>;
445
+ low_bandwidth: z.ZodOptional<z.ZodObject<{
446
+ mode: z.ZodOptional<z.ZodEnum<{
447
+ auto: "auto";
448
+ on: "on";
449
+ off: "off";
450
+ }>>;
451
+ enter_lag_ms: z.ZodOptional<z.ZodNumber>;
452
+ enter_after_s: z.ZodOptional<z.ZodNumber>;
453
+ exit_lag_ms: z.ZodOptional<z.ZodNumber>;
454
+ exit_after_s: z.ZodOptional<z.ZodNumber>;
455
+ datapoint_max_hz: z.ZodOptional<z.ZodNumber>;
456
+ camera: z.ZodOptional<z.ZodEnum<{
457
+ reduce: "reduce";
458
+ stop: "stop";
459
+ }>>;
460
+ camera_bitrate_kbps: z.ZodOptional<z.ZodNumber>;
461
+ }, z.core.$strict>>;
372
462
  }, z.core.$strict>>;
373
463
  source: z.ZodString;
374
464
  updated_at: z.ZodNullable<z.ZodISODateTime>;
@@ -439,6 +529,7 @@ export declare const configVersionResponse: z.ZodObject<{
439
529
  type: z.ZodString;
440
530
  field: z.ZodOptional<z.ZodString>;
441
531
  rate_throttle_hz: z.ZodOptional<z.ZodNumber>;
532
+ low_bandwidth: z.ZodOptional<z.ZodLiteral<"keep">>;
442
533
  description: z.ZodOptional<z.ZodString>;
443
534
  numeric: z.ZodOptional<z.ZodObject<{
444
535
  scale: z.ZodOptional<z.ZodNumber>;
@@ -605,6 +696,23 @@ export declare const configVersionResponse: z.ZodObject<{
605
696
  snapshot_interval_seconds: z.ZodNumber;
606
697
  description: z.ZodOptional<z.ZodString>;
607
698
  }, z.core.$strict>>>;
699
+ low_bandwidth: z.ZodOptional<z.ZodObject<{
700
+ mode: z.ZodOptional<z.ZodEnum<{
701
+ auto: "auto";
702
+ on: "on";
703
+ off: "off";
704
+ }>>;
705
+ enter_lag_ms: z.ZodOptional<z.ZodNumber>;
706
+ enter_after_s: z.ZodOptional<z.ZodNumber>;
707
+ exit_lag_ms: z.ZodOptional<z.ZodNumber>;
708
+ exit_after_s: z.ZodOptional<z.ZodNumber>;
709
+ datapoint_max_hz: z.ZodOptional<z.ZodNumber>;
710
+ camera: z.ZodOptional<z.ZodEnum<{
711
+ reduce: "reduce";
712
+ stop: "stop";
713
+ }>>;
714
+ camera_bitrate_kbps: z.ZodOptional<z.ZodNumber>;
715
+ }, z.core.$strict>>;
608
716
  }, z.core.$strict>;
609
717
  source: z.ZodString;
610
718
  }, z.core.$strip>;
@@ -1105,22 +1213,21 @@ export declare const ASSET_UPLOAD_HEADERS: {
1105
1213
  readonly nameEncoded: "x-fleetless-asset-name-encoded";
1106
1214
  readonly syncId: "x-fleetless-sync-id";
1107
1215
  /**
1108
- * **The announced size, and it is what makes `asset_too_large` reachable at
1109
- * all.**
1216
+ * **The announced size, and it is what makes a structured store refusal
1217
+ * reachable at all.**
1110
1218
  *
1111
1219
  * A server-side body limit is applied by the content-type parser, before the
1112
- * handler runs, so an oversized upload can only be refused with a bare
1113
- * `413` carrying neither `limit_bytes` nor `size_bytes` — and the structured
1114
- * refusal `assetTooLargeDetails` describes would have no producer.
1220
+ * handler runs, so an upload with no room left can only be refused with a
1221
+ * bare `413` carrying none of the three numbers — and the refusal
1222
+ * `assetStoreRefusedDetails` describes would have no producer.
1115
1223
  *
1116
- * With the size announced in a header the refusal can be made where it can
1117
- * say something: before a byte is buffered, with both numbers. It also lets
1118
- * a producer discover its own limit without first reading the whole file
1119
- * into memory.
1224
+ * With the size announced in a header the cloud can check `used + size`
1225
+ * against `ROBOT_ASSET_STORE_BYTES` where it can still say something: before
1226
+ * a byte is buffered, with all three numbers.
1120
1227
  *
1121
1228
  * The header is an **announcement, not a proof**: a sender can lie. The
1122
- * ceiling still applies to the body — this does not replace enforcement, it
1123
- * only makes the refusal answerable.
1229
+ * store still applies to the bytes that arrive — this does not replace
1230
+ * enforcement, it only makes the refusal answerable.
1124
1231
  */
1125
1232
  readonly size: "x-fleetless-asset-size";
1126
1233
  };
@@ -1248,8 +1355,8 @@ export declare const historySamplesResponse: z.ZodObject<{
1248
1355
  }, z.core.$strip>>;
1249
1356
  truncated: z.ZodBoolean;
1250
1357
  truncated_by: z.ZodNullable<z.ZodEnum<{
1251
- limit: "limit";
1252
1358
  bytes: "bytes";
1359
+ limit: "limit";
1253
1360
  }>>;
1254
1361
  }, z.core.$strip>;
1255
1362
  export type HistorySamplesResponse = z.infer<typeof historySamplesResponse>;
@@ -1307,8 +1414,8 @@ export declare const historyResponse: z.ZodUnion<readonly [z.ZodObject<{
1307
1414
  }, z.core.$strip>>;
1308
1415
  truncated: z.ZodBoolean;
1309
1416
  truncated_by: z.ZodNullable<z.ZodEnum<{
1310
- limit: "limit";
1311
1417
  bytes: "bytes";
1418
+ limit: "limit";
1312
1419
  }>>;
1313
1420
  }, z.core.$strip>, z.ZodObject<{
1314
1421
  slug: z.ZodString;
@@ -1519,7 +1626,6 @@ export declare const orgQuotas: z.ZodObject<{
1519
1626
  max_retention_bytes: z.ZodNumber;
1520
1627
  max_retention_writes_per_minute: z.ZodNumber;
1521
1628
  max_realtime_connections: z.ZodNumber;
1522
- max_asset_storage_bytes: z.ZodNumber;
1523
1629
  }, z.core.$strip>;
1524
1630
  export type OrgQuotas = z.infer<typeof orgQuotas>;
1525
1631
  /**
@@ -1542,7 +1648,6 @@ export declare const orgQuotaUsageCounts: z.ZodObject<{
1542
1648
  max_apps: z.ZodOptional<z.ZodNumber>;
1543
1649
  max_end_users: z.ZodOptional<z.ZodNumber>;
1544
1650
  max_retention_bytes: z.ZodOptional<z.ZodNumber>;
1545
- max_asset_storage_bytes: z.ZodOptional<z.ZodNumber>;
1546
1651
  max_retention_writes_per_minute: z.ZodOptional<z.ZodNumber>;
1547
1652
  max_realtime_connections: z.ZodOptional<z.ZodNumber>;
1548
1653
  }, z.core.$strip>;
@@ -1556,14 +1661,12 @@ export declare const orgQuotaUsage: z.ZodObject<{
1556
1661
  max_retention_bytes: z.ZodNumber;
1557
1662
  max_retention_writes_per_minute: z.ZodNumber;
1558
1663
  max_realtime_connections: z.ZodNumber;
1559
- max_asset_storage_bytes: z.ZodNumber;
1560
1664
  }, z.core.$strip>;
1561
1665
  usage: z.ZodObject<{
1562
1666
  max_robots: z.ZodOptional<z.ZodNumber>;
1563
1667
  max_apps: z.ZodOptional<z.ZodNumber>;
1564
1668
  max_end_users: z.ZodOptional<z.ZodNumber>;
1565
1669
  max_retention_bytes: z.ZodOptional<z.ZodNumber>;
1566
- max_asset_storage_bytes: z.ZodOptional<z.ZodNumber>;
1567
1670
  max_retention_writes_per_minute: z.ZodOptional<z.ZodNumber>;
1568
1671
  max_realtime_connections: z.ZodOptional<z.ZodNumber>;
1569
1672
  }, z.core.$strip>;
@@ -1699,8 +1802,8 @@ export declare const orgLatencyResponse: z.ZodObject<{
1699
1802
  to_ms: z.ZodNumber;
1700
1803
  truncated: z.ZodBoolean;
1701
1804
  truncated_by: z.ZodNullable<z.ZodEnum<{
1702
- limit: "limit";
1703
1805
  bytes: "bytes";
1806
+ limit: "limit";
1704
1807
  }>>;
1705
1808
  }, z.core.$strip>;
1706
1809
  export type OrgLatencyResponse = z.infer<typeof orgLatencyResponse>;
@@ -1716,11 +1819,10 @@ export declare const USAGE_WINDOW_MAX_DAYS = 366;
1716
1819
  /**
1717
1820
  * The five things the meter records.
1718
1821
  *
1719
- * Storage is two metrics and not one summed byte count, for
1720
- * `org_quotas.max_asset_storage_bytes`'s own reason applied to billing: a sync
1721
- * grows storage in jumps and time series grow steadily, and one number would
1722
- * let the first crowd out the second on the invoice the same way it would on
1723
- * the quota.
1822
+ * Storage is two metrics and not one summed byte count: a sync grows storage
1823
+ * in jumps and time series grow steadily, and one number would let the first
1824
+ * crowd out the second on the invoice. The org that outgrew its bill would be
1825
+ * told to look at the wrong thing.
1724
1826
  */
1725
1827
  export declare const usageMetric: z.ZodEnum<{
1726
1828
  api_calls: "api_calls";
package/dist/rest.js CHANGED
@@ -38,6 +38,42 @@ export const createRobotResponse = z.object({
38
38
  robot,
39
39
  token: robotToken,
40
40
  });
41
+ /**
42
+ * What a rotation hands back: the new token, once.
43
+ *
44
+ * The same shape as the creation response minus the robot, because nothing
45
+ * about the robot changed — only its credential. `createRobotResponse`'s own
46
+ * rule applies unchanged: the cloud stores a hash, so this is the only moment
47
+ * the raw token exists outside the caller's hands.
48
+ */
49
+ export const robotTokenRotateResponse = z.object({
50
+ token: robotToken.meta({
51
+ description: 'The robot\'s new bridge token. Returned exactly once; the previous token stops working at the bridge\'s next hello.',
52
+ }),
53
+ });
54
+ /**
55
+ * Which datapoint drives the joints of this robot's URDF, or none.
56
+ *
57
+ * `null` is the clearing value, which is why `slug` is required rather than
58
+ * optional: an absent field and a cleared mapping would be the same request
59
+ * and mean different things, and the one a client sends by accident is the
60
+ * first.
61
+ *
62
+ * The cloud refuses a slug that is not a whole-message
63
+ * `sensor_msgs/msg/JointState` datapoint of the **published** document — a
64
+ * mapping that may point anywhere is a viewer animating a battery reading.
65
+ */
66
+ export const jointStatePutRequest = z.object({
67
+ slug: slug.nullable().meta({
68
+ description: 'The datapoint to read joint positions from, or `null` to choose none. It must name a whole-message `sensor_msgs/msg/JointState` datapoint of the published configuration; anything else is a `validation_error` naming the rule.',
69
+ }),
70
+ });
71
+ /** The mapping as it now stands — the same field `GET /api/robots/:id/assets` reports. */
72
+ export const jointStatePutResponse = z.object({
73
+ joint_state_slug: slug.nullable().meta({
74
+ description: 'The stored mapping after the call, `null` when none is chosen. The same value `assetListResponse.joint_state_slug` carries.',
75
+ }),
76
+ });
41
77
  /**
42
78
  * How many things a robot exposes, per kind.
43
79
  *
@@ -47,18 +83,17 @@ export const createRobotResponse = z.object({
47
83
  *
48
84
  * **Counted from the published configuration, and excluding the built-ins.**
49
85
  * `GET /api/robots/:id/exposures` answers *which* slugs and prepends the
50
- * three built-in datapoints — `bridge_state`, `robot_details` and
51
- * `bridge_pressure` — as `builtin: true`; this answers *how many* and counts
52
- * only what somebody configured. So a robot with an empty published config
53
- * reports `datapoints: 0` here and three entries there. That is intentional,
54
- * and it is written on both sides so the disagreement is never mistaken for a
55
- * bug.
56
- *
57
- * The number is "three" and not "two" as of `bridge_pressure`; the cloud
58
- * builds that prefix from `PLANE_BUILTIN_DATAPOINTS` rather than a literal,
59
- * so a further built-in moves this count again. Read the count off that set,
60
- * not off this sentence, before filing the bug this comment exists to
61
- * prevent.
86
+ * built-in datapoints — `bridge_state` and `robot_details` — as
87
+ * `builtin: true`; this answers *how many* and counts only what somebody
88
+ * configured. So a robot with an empty published config reports
89
+ * `datapoints: 0` here and two entries there. That is intentional, and it is
90
+ * written on both sides so the disagreement is never mistaken for a bug.
91
+ *
92
+ * The number was "three" while `bridge_pressure` existed and is "two" since
93
+ * protocol 3 dropped it; the cloud builds that prefix from its own built-in
94
+ * set rather than a literal, so the next built-in moves this count again.
95
+ * Read the count off that set, not off this sentence, before filing the bug
96
+ * this comment exists to prevent.
62
97
  */
63
98
  export const exposureCounts = z.object({
64
99
  datapoints: z.number().int().nonnegative(),
@@ -67,12 +102,22 @@ export const exposureCounts = z.object({
67
102
  publishers: z.number().int().nonnegative(),
68
103
  cameras: z.number().int().nonnegative(),
69
104
  });
105
+ /**
106
+ * Where a robot's bridge stands against the protocol window.
107
+ * `refused`: its last hello was refused for its version — it is offline
108
+ * until upgraded. Computed by the cloud from `protocol_version` and
109
+ * `last_hello_error`, never stored.
110
+ */
111
+ export const protocolStatusValue = z.enum(['current', 'deprecated', 'refused']);
70
112
  /** A robot as listed, with its current built-in `bridge_state`. */
71
113
  export const robotListItem = z.object({
72
114
  ...robot.shape,
73
115
  bridge_state: bridgeState,
74
116
  /** Required, not optional: "we did not look" and "it exposes nothing" must not render the same. */
75
117
  exposes: exposureCounts,
118
+ protocol_status: protocolStatusValue.optional().meta({
119
+ description: 'Where this robot\'s bridge stands against the protocol window: `current`, `deprecated` (still served, sunset date on the detail), or `refused` (its last hello was refused for its version; offline until upgraded). Absent from a cloud older than 0.21.0; read absence as `current`.',
120
+ }),
76
121
  });
77
122
  export const robotListResponse = z.object({
78
123
  robots: z.array(robotListItem),
@@ -103,6 +148,18 @@ export const datapointValue = z.object({
103
148
  export const robotDetailResponse = z.object({
104
149
  ...robotListItem.shape,
105
150
  bridge_version: z.string().min(1).nullable(),
151
+ protocol_version: z.number().int().positive().nullable().optional().meta({
152
+ description: 'The protocol version the bridge announced in its last accepted hello; `null` before the first. Absent from a cloud older than 0.21.0.',
153
+ }),
154
+ protocol: z
155
+ .object({
156
+ status: protocolStatusValue.meta({ description: 'Same values as `protocol_status`.' }),
157
+ sunset_at: z.iso.date().nullable().meta({
158
+ description: 'ISO date the announced version stops being served; `null` when current or unknown.',
159
+ }),
160
+ })
161
+ .optional()
162
+ .meta({ description: 'The window verdict for `protocol_version`.' }),
106
163
  /**
107
164
  * Cleared (set back to null) by the next successful hello from this
108
165
  * robot's bridge — a warning that outlives the condition it warns
@@ -232,7 +289,7 @@ export const fetchTypesResponse = z.object({
232
289
  export const datapointDescriptor = z.object({
233
290
  slug: slug.meta({ description: 'The name a client reads this datapoint by.' }),
234
291
  builtin: z.boolean().meta({
235
- description: '`true` for the datapoints every robot has — `bridge_state`, `robot_details` and `bridge_pressure` — and `false` for everything the published configuration adds.',
292
+ description: '`true` for the datapoints every robot has — `bridge_state` and `robot_details` — and `false` for everything the published configuration adds.',
236
293
  }),
237
294
  unit: z.string().nullable().meta({
238
295
  description: 'The unit the value carries **after** any scale and offset, shown beside the number so nobody has to guess whether `15` means percent, volts or minutes. `null` when the configuration names none.',
@@ -250,7 +307,7 @@ export const datapointDescriptor = z.object({
250
307
  });
251
308
  export const datapointListResponse = z.object({
252
309
  datapoints: z.array(datapointDescriptor).meta({
253
- description: 'Everything a client may read on this robot: the three built-ins, plus every datapoint the published configuration exposes and the caller\'s role grants.',
310
+ description: 'Everything a client may read on this robot: the two built-ins, plus every datapoint the published configuration exposes and the caller\'s role grants.',
254
311
  }),
255
312
  });
256
313
  /**
@@ -801,22 +858,21 @@ export const ASSET_UPLOAD_HEADERS = {
801
858
  nameEncoded: 'x-fleetless-asset-name-encoded',
802
859
  syncId: 'x-fleetless-sync-id',
803
860
  /**
804
- * **The announced size, and it is what makes `asset_too_large` reachable at
805
- * all.**
861
+ * **The announced size, and it is what makes a structured store refusal
862
+ * reachable at all.**
806
863
  *
807
864
  * A server-side body limit is applied by the content-type parser, before the
808
- * handler runs, so an oversized upload can only be refused with a bare
809
- * `413` carrying neither `limit_bytes` nor `size_bytes` — and the structured
810
- * refusal `assetTooLargeDetails` describes would have no producer.
865
+ * handler runs, so an upload with no room left can only be refused with a
866
+ * bare `413` carrying none of the three numbers — and the refusal
867
+ * `assetStoreRefusedDetails` describes would have no producer.
811
868
  *
812
- * With the size announced in a header the refusal can be made where it can
813
- * say something: before a byte is buffered, with both numbers. It also lets
814
- * a producer discover its own limit without first reading the whole file
815
- * into memory.
869
+ * With the size announced in a header the cloud can check `used + size`
870
+ * against `ROBOT_ASSET_STORE_BYTES` where it can still say something: before
871
+ * a byte is buffered, with all three numbers.
816
872
  *
817
873
  * The header is an **announcement, not a proof**: a sender can lie. The
818
- * ceiling still applies to the body — this does not replace enforcement, it
819
- * only makes the refusal answerable.
874
+ * store still applies to the bytes that arrive — this does not replace
875
+ * enforcement, it only makes the refusal answerable.
820
876
  */
821
877
  size: 'x-fleetless-asset-size',
822
878
  };
@@ -1233,22 +1289,25 @@ export const robotDeletionSummary = z.object({
1233
1289
  bytes_freed: z.number().int().nonnegative(),
1234
1290
  cameras: z.array(slug),
1235
1291
  /**
1236
- * Assets destroyed with the robot, and **`asset_bytes_freed` is what
1237
- * this org actually gets back** — not the sum of the assets' sizes.
1292
+ * Assets destroyed with the robot, and **`asset_bytes_freed` is what the
1293
+ * robot's own store gives back** — every distinct mesh or texture blob it
1294
+ * holds, counted once, URDF excluded.
1238
1295
  *
1239
- * Storage is content-addressed, so a mesh two robots share survives the
1240
- * deletion of one of them and frees nothing. Reporting the total would tell
1241
- * a developer they are about to recover 400 MB and hand back 4, on the one
1242
- * screen whose entire justification is naming what an irreversible click
1243
- * destroys. Same reasoning that keeps `cameras` out of `slug_count`: this
1244
- * summary is read aloud to a human, and a number that is nearly right is
1245
- * worse here than an absent one.
1296
+ * Storage is content-addressed, but the store and its 1 GB ceiling are now
1297
+ * per robot: a blob another robot also references stays in the object
1298
+ * store but is still credited here, because each robot's counter carries
1299
+ * it regardless of what else points at the same bytes. Same reasoning that
1300
+ * keeps `cameras` out of `slug_count`: this summary is read aloud to a
1301
+ * human, and a number that is nearly right is worse here than an absent
1302
+ * one.
1246
1303
  *
1247
1304
  * `asset_count` is the plain count of the robot's asset rows, all of which
1248
1305
  * do go away.
1249
1306
  */
1250
1307
  asset_count: z.number().int().nonnegative(),
1251
- asset_bytes_freed: z.number().int().nonnegative(),
1308
+ asset_bytes_freed: z.number().int().nonnegative().meta({
1309
+ description: 'What the robot\'s store gives back: every distinct mesh or texture blob it holds, counted once, URDF excluded; a blob another robot also references stays in the object store but is still credited here, because each robot\'s counter carries it.',
1310
+ }),
1252
1311
  /**
1253
1312
  * How many rows of run history go with the robot — every recorded
1254
1313
  * invocation of one of its actions or services, up to
@@ -1460,31 +1519,6 @@ export const orgQuotas = z.object({
1460
1519
  max_retention_bytes: z.number().int().nonnegative(),
1461
1520
  max_retention_writes_per_minute: z.number().int().nonnegative(),
1462
1521
  max_realtime_connections: z.number().int().positive(),
1463
- /**
1464
- * Asset storage — **its own dial, not part of `max_retention_bytes`.** A
1465
- * sync grows storage in jumps and time series grow steadily; one dial would
1466
- * let the first crowd out the second, and the org that hit its limit would be
1467
- * told to look at the wrong thing.
1468
- *
1469
- * **Counted per distinct blob *this org references* — not per asset row, and
1470
- * not per object the platform stores on its behalf.** The two readings are
1471
- * indistinguishable from the number alone and a customer is entitled to know
1472
- * which one they are being charged for.
1473
- *
1474
- * Within an org, sharing is free: two robots referencing the same mesh cost
1475
- * one copy, which is what dedup means to a customer, and anything else
1476
- * charges an org twice for a fleet of identical robots — the normal case.
1477
- *
1478
- * **Across orgs, sharing is not free.** Storage stays globally
1479
- * content-addressed (one object per sha256; that efficiency is real), but
1480
- * accounting is per-org: an org is charged for each distinct blob it
1481
- * references and credited when its own last reference goes, whether or not
1482
- * the blob survives for somebody else. Global refcounting would make the
1483
- * first org to sync a blob pay for it forever while every later org stored it
1484
- * free — a quota evadable by anyone whose mesh someone else had already
1485
- * uploaded, and an org's own number would depend on who got there first.
1486
- */
1487
- max_asset_storage_bytes: z.number().int().nonnegative(),
1488
1522
  });
1489
1523
  /**
1490
1524
  * What an org is **actually using**, per quota.
@@ -1506,7 +1540,6 @@ export const orgQuotaUsageCounts = z.object({
1506
1540
  max_apps: z.number().int().nonnegative(),
1507
1541
  max_end_users: z.number().int().nonnegative(),
1508
1542
  max_retention_bytes: z.number().int().nonnegative(),
1509
- max_asset_storage_bytes: z.number().int().nonnegative(),
1510
1543
  max_retention_writes_per_minute: z.number().int().nonnegative(),
1511
1544
  max_realtime_connections: z.number().int().nonnegative(),
1512
1545
  }).partial();
@@ -1685,11 +1718,10 @@ export const USAGE_WINDOW_MAX_DAYS = 366;
1685
1718
  /**
1686
1719
  * The five things the meter records.
1687
1720
  *
1688
- * Storage is two metrics and not one summed byte count, for
1689
- * `org_quotas.max_asset_storage_bytes`'s own reason applied to billing: a sync
1690
- * grows storage in jumps and time series grow steadily, and one number would
1691
- * let the first crowd out the second on the invoice the same way it would on
1692
- * the quota.
1721
+ * Storage is two metrics and not one summed byte count: a sync grows storage
1722
+ * in jumps and time series grow steadily, and one number would let the first
1723
+ * crowd out the second on the invoice. The org that outgrew its bill would be
1724
+ * told to look at the wrong thing.
1693
1725
  */
1694
1726
  export const usageMetric = z.enum(['api_calls', 'live_session_ms', 'retention_bytes', 'asset_bytes', 'robot_online_ms']);
1695
1727
  /**