@fleetless/contracts 1.1.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 (66) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/artifacts/constants.json +30 -4
  3. package/artifacts/openapi.json +667 -98
  4. package/artifacts/routes.json +97 -6
  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/authorization-server-metadata.schema.json +1 -1
  11. package/artifacts/schema/bridge-asset-progress.schema.json +14 -8
  12. package/artifacts/schema/bridge-config-applied.schema.json +2 -1
  13. package/artifacts/schema/bridge-link-mode.schema.json +36 -0
  14. package/artifacts/schema/bridge-state.schema.json +6 -1
  15. package/artifacts/schema/client-robot-list-item.schema.json +6 -1
  16. package/artifacts/schema/client-robot-list-response.schema.json +6 -1
  17. package/artifacts/schema/cloud-config.schema.json +90 -5
  18. package/artifacts/schema/cloud-hello-ok.schema.json +45 -0
  19. package/artifacts/schema/cloud-ping.schema.json +27 -1
  20. package/artifacts/schema/config-draft-response.schema.json +90 -5
  21. package/artifacts/schema/config-state.schema.json +2 -1
  22. package/artifacts/schema/config-version-response.schema.json +90 -5
  23. package/artifacts/schema/datapoint-config.schema.json +5 -0
  24. package/artifacts/schema/datapoint-frame.schema.json +4 -0
  25. package/artifacts/schema/datapoint-list-response.schema.json +2 -2
  26. package/artifacts/schema/dynamic-client-registration-request.schema.json +1 -1
  27. package/artifacts/schema/dynamic-client-registration-response.schema.json +1 -1
  28. package/artifacts/schema/joint-state-put-request.schema.json +23 -0
  29. package/artifacts/schema/joint-state-put-response.schema.json +24 -0
  30. package/artifacts/schema/oauth-token-request.schema.json +79 -41
  31. package/artifacts/schema/oauth-token-response.schema.json +1 -1
  32. package/artifacts/schema/org-quota-usage-counts.schema.json +0 -5
  33. package/artifacts/schema/org-quota-usage.schema.json +1 -12
  34. package/artifacts/schema/org-quotas.schema.json +1 -7
  35. package/artifacts/schema/robot-config-doc.schema.json +90 -5
  36. package/artifacts/schema/robot-deletion-summary.schema.json +2 -1
  37. package/artifacts/schema/robot-detail-response.schema.json +63 -2
  38. package/artifacts/schema/robot-list-item.schema.json +15 -1
  39. package/artifacts/schema/robot-list-response.schema.json +15 -1
  40. package/artifacts/schema/robot-token-rotate-response.schema.json +15 -0
  41. package/artifacts/schema-outgoing/bridge-asset-progress.schema.json +14 -8
  42. package/artifacts/schema-outgoing/bridge-config-applied.schema.json +2 -1
  43. package/artifacts/schema-outgoing/bridge-link-mode.schema.json +37 -0
  44. package/artifacts/schema-outgoing/datapoint-frame.schema.json +4 -0
  45. package/dist/assets.d.ts +85 -50
  46. package/dist/assets.js +152 -62
  47. package/dist/audit.d.ts +1 -1
  48. package/dist/audit.js +1 -1
  49. package/dist/client-robots.d.ts +2 -0
  50. package/dist/common.d.ts +10 -0
  51. package/dist/common.js +16 -1
  52. package/dist/config.d.ts +69 -1
  53. package/dist/config.js +86 -6
  54. package/dist/errors.d.ts +1 -1
  55. package/dist/errors.js +1 -8
  56. package/dist/index.d.ts +10 -10
  57. package/dist/index.js +5 -5
  58. package/dist/oauth.d.ts +34 -19
  59. package/dist/oauth.js +39 -24
  60. package/dist/protocol.d.ts +150 -71
  61. package/dist/protocol.js +144 -87
  62. package/dist/rest.d.ts +137 -35
  63. package/dist/rest.js +98 -66
  64. package/dist/routes.js +68 -19
  65. package/package.json +1 -1
  66. package/artifacts/schema/bridge-pressure.schema.json +0 -292
@@ -117,6 +117,11 @@
117
117
  0.5
118
118
  ]
119
119
  },
120
+ "low_bandwidth": {
121
+ "description": "`keep` exempts this datapoint from the low-bandwidth rate cap; its backfill still pauses.",
122
+ "type": "string",
123
+ "const": "keep"
124
+ },
120
125
  "description": {
121
126
  "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
127
  "examples": [
@@ -437,7 +442,7 @@
437
442
  }
438
443
  ]
439
444
  },
440
- "description": "Values the robot publishes, each one field of one topic or a whole topic, and **never several topics**. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
445
+ "description": "Values the robot publishes, each one field of one topic or a whole topic, and **never several topics**. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
441
446
  "defaultSnippets": [
442
447
  {
443
448
  "label": "a datapoint",
@@ -685,7 +690,7 @@
685
690
  }
686
691
  ]
687
692
  },
688
- "description": "Things the robot does on request that take time, each reported as a job with progress. **At most one job runs per action slug**: a second call is refused `busy`, and every observer of that slug watches the same job. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
693
+ "description": "Things the robot does on request that take time, each reported as a job with progress. **At most one job runs per action slug**: a second call is refused `busy`, and every observer of that slug watches the same job. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
689
694
  "defaultSnippets": [
690
695
  {
691
696
  "label": "an action",
@@ -906,7 +911,7 @@
906
911
  }
907
912
  ]
908
913
  },
909
- "description": "ROS service calls the robot answers — one request, one reply. Unlike an action a service reports **no progress** and the call returns with its result already on the job, so there is nothing left to observe; a second concurrent call is still refused `busy`, exactly as for an action. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
914
+ "description": "ROS service calls the robot answers — one request, one reply. Unlike an action a service reports **no progress** and the call returns with its result already on the job, so there is nothing left to observe; a second concurrent call is still refused `busy`, exactly as for an action. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
910
915
  "defaultSnippets": [
911
916
  {
912
917
  "label": "a service",
@@ -1214,7 +1219,7 @@
1214
1219
  }
1215
1220
  ]
1216
1221
  },
1217
- "description": "Topics clients may send to, and where the format's whole safety story lives. The `message` template fixes every value a caller cannot change, and **`failsafe` is required**: once a client falls silent the bridge sends the failsafe message itself, so an operator whose window closed does not leave a robot driving. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
1222
+ "description": "Topics clients may send to, and where the format's whole safety story lives. The `message` template fixes every value a caller cannot change, and **`failsafe` is required**: once a client falls silent the bridge sends the failsafe message itself, so an operator whose window closed does not leave a robot driving. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
1218
1223
  "defaultSnippets": [
1219
1224
  {
1220
1225
  "label": "a publisher, with its parameters and its failsafe",
@@ -1598,7 +1603,7 @@
1598
1603
  }
1599
1604
  ]
1600
1605
  },
1601
- "description": "Video the robot streams, and the still frames the cloud serves from it. `width`, `height`, `fps` and `bitrate_kbps` are what **the bridge produces before sending**, not what the camera captures — they live in the configuration rather than in a viewer's request precisely so that no viewer can make a robot send more. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
1606
+ "description": "Video the robot streams, and the still frames the cloud serves from it. `width`, `height`, `fps` and `bitrate_kbps` are what **the bridge produces before sending**, not what the camera captures — they live in the configuration rather than in a viewer's request precisely so that no viewer can make a robot send more. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
1602
1607
  "defaultSnippets": [
1603
1608
  {
1604
1609
  "label": "a camera",
@@ -1619,6 +1624,86 @@
1619
1624
  }
1620
1625
  }
1621
1626
  ]
1627
+ },
1628
+ "low_bandwidth": {
1629
+ "description": "Overrides for the bridge's low-bandwidth mode; see the section schema.",
1630
+ "defaultSnippets": [
1631
+ {
1632
+ "label": "low-bandwidth mode, tuned",
1633
+ "description": "Enters after ten seconds above two seconds of lag, caps every datapoint to 1 Hz and lets running streams continue at a reduced bitrate.",
1634
+ "body": {
1635
+ "enter_lag_ms": 2000,
1636
+ "datapoint_max_hz": 1,
1637
+ "camera": "reduce"
1638
+ }
1639
+ }
1640
+ ],
1641
+ "type": "object",
1642
+ "properties": {
1643
+ "mode": {
1644
+ "description": "`auto` decides from the measured lag; `on` and `off` force the mode, for tests and for an operator who knows the link.",
1645
+ "enumDescriptions": [
1646
+ "The bridge enters and leaves the mode on its own, from the lag the cloud reports and the dwell it measures in its own send queue. The setting to leave alone.",
1647
+ "The mode is held on, whatever the link is doing. For a robot on a link known to be poor, and for a test that would otherwise have to wait for a real one.",
1648
+ "The mode never engages, whatever the link is doing. The robot then sends at its configured rates over a link that cannot carry them, which is a choice and not a default."
1649
+ ],
1650
+ "type": "string",
1651
+ "enum": [
1652
+ "auto",
1653
+ "on",
1654
+ "off"
1655
+ ]
1656
+ },
1657
+ "enter_lag_ms": {
1658
+ "description": "Lag or queue dwell above this enters the mode. Checked against exit_lag_ms only when both are in this document; a lone key composes with the bridge's parameter or the default on the robot, and a crossed pair is refused there when the configuration is applied, so name both when you change either.",
1659
+ "type": "integer",
1660
+ "minimum": 100,
1661
+ "maximum": 9007199254740991
1662
+ },
1663
+ "enter_after_s": {
1664
+ "description": "The entry condition must hold this long.",
1665
+ "type": "integer",
1666
+ "minimum": 1,
1667
+ "maximum": 9007199254740991
1668
+ },
1669
+ "exit_lag_ms": {
1670
+ "description": "Lag and dwell both at or below this leave the mode. Must be at or below enter_lag_ms: a crossed pair is a mode that leaves as it arrives. Checked here only when both keys are present; a lone key is checked on the robot against the parameter or default it composes with.",
1671
+ "type": "integer",
1672
+ "minimum": 0,
1673
+ "maximum": 9007199254740991
1674
+ },
1675
+ "exit_after_s": {
1676
+ "description": "The exit condition must hold this long.",
1677
+ "type": "integer",
1678
+ "minimum": 1,
1679
+ "maximum": 9007199254740991
1680
+ },
1681
+ "datapoint_max_hz": {
1682
+ "description": "The long-run rate for every datapoint in the mode, unless the datapoint says `low_bandwidth: keep`. It is an average, not a minimum gap: after a quiet spell two samples may go out close together, and over any longer window the rate holds.",
1683
+ "type": "number",
1684
+ "exclusiveMinimum": 0,
1685
+ "maximum": 20
1686
+ },
1687
+ "camera": {
1688
+ "description": "What happens to a running stream in the mode. New streams are refused either way.",
1689
+ "enumDescriptions": [
1690
+ "A running stream is re-encoded at `camera_bitrate_kbps` and keeps running. A viewer sees a worse picture rather than none.",
1691
+ "A running stream ends and the viewer is told why. The uplink is then free for datapoints, which is the right trade where video is the nice-to-have."
1692
+ ],
1693
+ "type": "string",
1694
+ "enum": [
1695
+ "reduce",
1696
+ "stop"
1697
+ ]
1698
+ },
1699
+ "camera_bitrate_kbps": {
1700
+ "description": "Bitrate applied to running streams under `reduce`.",
1701
+ "type": "integer",
1702
+ "minimum": 50,
1703
+ "maximum": 20000
1704
+ }
1705
+ },
1706
+ "additionalProperties": false
1622
1707
  }
1623
1708
  },
1624
1709
  "required": [
@@ -10,6 +10,51 @@
10
10
  "type": "string",
11
11
  "format": "uuid",
12
12
  "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)$"
13
+ },
14
+ "protocol": {
15
+ "description": "The cloud's verdict on the announced protocol version; absent from an older cloud.",
16
+ "type": "object",
17
+ "properties": {
18
+ "status": {
19
+ "type": "string",
20
+ "enum": [
21
+ "current",
22
+ "deprecated"
23
+ ],
24
+ "description": "`current` or `deprecated` — never `unsupported`, which is a `hello_error`."
25
+ },
26
+ "sunset_at": {
27
+ "anyOf": [
28
+ {
29
+ "type": "string",
30
+ "format": "date",
31
+ "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])))$"
32
+ },
33
+ {
34
+ "type": "null"
35
+ }
36
+ ],
37
+ "description": "ISO date a deprecated version stops being served; `null` when current."
38
+ }
39
+ },
40
+ "required": [
41
+ "status",
42
+ "sunset_at"
43
+ ]
44
+ },
45
+ "bridge": {
46
+ "description": "What the cloud knows about bridge packages; absent from an older cloud.",
47
+ "type": "object",
48
+ "properties": {
49
+ "latest_version": {
50
+ "type": "string",
51
+ "minLength": 1,
52
+ "description": "The newest published fleetless-bridge package version, for the bridge's own upgrade hint."
53
+ }
54
+ },
55
+ "required": [
56
+ "latest_version"
57
+ ]
13
58
  }
14
59
  },
15
60
  "required": [
@@ -10,10 +10,36 @@
10
10
  "type": "integer",
11
11
  "minimum": 0,
12
12
  "maximum": 9007199254740991
13
+ },
14
+ "latency_ms": {
15
+ "anyOf": [
16
+ {
17
+ "type": "number",
18
+ "minimum": 0
19
+ },
20
+ {
21
+ "type": "null"
22
+ }
23
+ ],
24
+ "description": "Round trip of the last pong in milliseconds; null before the first."
25
+ },
26
+ "lag_ms": {
27
+ "anyOf": [
28
+ {
29
+ "type": "number",
30
+ "minimum": 0
31
+ },
32
+ {
33
+ "type": "null"
34
+ }
35
+ ],
36
+ "description": "Datapoint lag over the link: median of the last five seconds minus the ten-minute minimum, in milliseconds; null until a sample exists, and null again whenever no live sample arrived in the last five seconds, because a stale median would be a lie."
13
37
  }
14
38
  },
15
39
  "required": [
16
40
  "type",
17
- "ts_ms"
41
+ "ts_ms",
42
+ "latency_ms",
43
+ "lag_ms"
18
44
  ]
19
45
  }
@@ -110,6 +110,11 @@
110
110
  0.5
111
111
  ]
112
112
  },
113
+ "low_bandwidth": {
114
+ "description": "`keep` exempts this datapoint from the low-bandwidth rate cap; its backfill still pauses.",
115
+ "type": "string",
116
+ "const": "keep"
117
+ },
113
118
  "description": {
114
119
  "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.",
115
120
  "examples": [
@@ -430,7 +435,7 @@
430
435
  }
431
436
  ]
432
437
  },
433
- "description": "Values the robot publishes, each one field of one topic or a whole topic, and **never several topics**. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
438
+ "description": "Values the robot publishes, each one field of one topic or a whole topic, and **never several topics**. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
434
439
  "defaultSnippets": [
435
440
  {
436
441
  "label": "a datapoint",
@@ -678,7 +683,7 @@
678
683
  }
679
684
  ]
680
685
  },
681
- "description": "Things the robot does on request that take time, each reported as a job with progress. **At most one job runs per action slug**: a second call is refused `busy`, and every observer of that slug watches the same job. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
686
+ "description": "Things the robot does on request that take time, each reported as a job with progress. **At most one job runs per action slug**: a second call is refused `busy`, and every observer of that slug watches the same job. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
682
687
  "defaultSnippets": [
683
688
  {
684
689
  "label": "an action",
@@ -899,7 +904,7 @@
899
904
  }
900
905
  ]
901
906
  },
902
- "description": "ROS service calls the robot answers — one request, one reply. Unlike an action a service reports **no progress** and the call returns with its result already on the job, so there is nothing left to observe; a second concurrent call is still refused `busy`, exactly as for an action. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
907
+ "description": "ROS service calls the robot answers — one request, one reply. Unlike an action a service reports **no progress** and the call returns with its result already on the job, so there is nothing left to observe; a second concurrent call is still refused `busy`, exactly as for an action. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
903
908
  "defaultSnippets": [
904
909
  {
905
910
  "label": "a service",
@@ -1207,7 +1212,7 @@
1207
1212
  }
1208
1213
  ]
1209
1214
  },
1210
- "description": "Topics clients may send to, and where the format's whole safety story lives. The `message` template fixes every value a caller cannot change, and **`failsafe` is required**: once a client falls silent the bridge sends the failsafe message itself, so an operator whose window closed does not leave a robot driving. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
1215
+ "description": "Topics clients may send to, and where the format's whole safety story lives. The `message` template fixes every value a caller cannot change, and **`failsafe` is required**: once a client falls silent the bridge sends the failsafe message itself, so an operator whose window closed does not leave a robot driving. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
1211
1216
  "defaultSnippets": [
1212
1217
  {
1213
1218
  "label": "a publisher, with its parameters and its failsafe",
@@ -1591,7 +1596,7 @@
1591
1596
  }
1592
1597
  ]
1593
1598
  },
1594
- "description": "Video the robot streams, and the still frames the cloud serves from it. `width`, `height`, `fps` and `bitrate_kbps` are what **the bridge produces before sending**, not what the camera captures — they live in the configuration rather than in a viewer's request precisely so that no viewer can make a robot send more. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
1599
+ "description": "Video the robot streams, and the still frames the cloud serves from it. `width`, `height`, `fps` and `bitrate_kbps` are what **the bridge produces before sending**, not what the camera captures — they live in the configuration rather than in a viewer's request precisely so that no viewer can make a robot send more. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
1595
1600
  "defaultSnippets": [
1596
1601
  {
1597
1602
  "label": "a camera",
@@ -1612,6 +1617,86 @@
1612
1617
  }
1613
1618
  }
1614
1619
  ]
1620
+ },
1621
+ "low_bandwidth": {
1622
+ "description": "Overrides for the bridge's low-bandwidth mode; see the section schema.",
1623
+ "defaultSnippets": [
1624
+ {
1625
+ "label": "low-bandwidth mode, tuned",
1626
+ "description": "Enters after ten seconds above two seconds of lag, caps every datapoint to 1 Hz and lets running streams continue at a reduced bitrate.",
1627
+ "body": {
1628
+ "enter_lag_ms": 2000,
1629
+ "datapoint_max_hz": 1,
1630
+ "camera": "reduce"
1631
+ }
1632
+ }
1633
+ ],
1634
+ "type": "object",
1635
+ "properties": {
1636
+ "mode": {
1637
+ "description": "`auto` decides from the measured lag; `on` and `off` force the mode, for tests and for an operator who knows the link.",
1638
+ "enumDescriptions": [
1639
+ "The bridge enters and leaves the mode on its own, from the lag the cloud reports and the dwell it measures in its own send queue. The setting to leave alone.",
1640
+ "The mode is held on, whatever the link is doing. For a robot on a link known to be poor, and for a test that would otherwise have to wait for a real one.",
1641
+ "The mode never engages, whatever the link is doing. The robot then sends at its configured rates over a link that cannot carry them, which is a choice and not a default."
1642
+ ],
1643
+ "type": "string",
1644
+ "enum": [
1645
+ "auto",
1646
+ "on",
1647
+ "off"
1648
+ ]
1649
+ },
1650
+ "enter_lag_ms": {
1651
+ "description": "Lag or queue dwell above this enters the mode. Checked against exit_lag_ms only when both are in this document; a lone key composes with the bridge's parameter or the default on the robot, and a crossed pair is refused there when the configuration is applied, so name both when you change either.",
1652
+ "type": "integer",
1653
+ "minimum": 100,
1654
+ "maximum": 9007199254740991
1655
+ },
1656
+ "enter_after_s": {
1657
+ "description": "The entry condition must hold this long.",
1658
+ "type": "integer",
1659
+ "minimum": 1,
1660
+ "maximum": 9007199254740991
1661
+ },
1662
+ "exit_lag_ms": {
1663
+ "description": "Lag and dwell both at or below this leave the mode. Must be at or below enter_lag_ms: a crossed pair is a mode that leaves as it arrives. Checked here only when both keys are present; a lone key is checked on the robot against the parameter or default it composes with.",
1664
+ "type": "integer",
1665
+ "minimum": 0,
1666
+ "maximum": 9007199254740991
1667
+ },
1668
+ "exit_after_s": {
1669
+ "description": "The exit condition must hold this long.",
1670
+ "type": "integer",
1671
+ "minimum": 1,
1672
+ "maximum": 9007199254740991
1673
+ },
1674
+ "datapoint_max_hz": {
1675
+ "description": "The long-run rate for every datapoint in the mode, unless the datapoint says `low_bandwidth: keep`. It is an average, not a minimum gap: after a quiet spell two samples may go out close together, and over any longer window the rate holds.",
1676
+ "type": "number",
1677
+ "exclusiveMinimum": 0,
1678
+ "maximum": 20
1679
+ },
1680
+ "camera": {
1681
+ "description": "What happens to a running stream in the mode. New streams are refused either way.",
1682
+ "enumDescriptions": [
1683
+ "A running stream is re-encoded at `camera_bitrate_kbps` and keeps running. A viewer sees a worse picture rather than none.",
1684
+ "A running stream ends and the viewer is told why. The uplink is then free for datapoints, which is the right trade where video is the nice-to-have."
1685
+ ],
1686
+ "type": "string",
1687
+ "enum": [
1688
+ "reduce",
1689
+ "stop"
1690
+ ]
1691
+ },
1692
+ "camera_bitrate_kbps": {
1693
+ "description": "Bitrate applied to running streams under `reduce`.",
1694
+ "type": "integer",
1695
+ "minimum": 50,
1696
+ "maximum": 20000
1697
+ }
1698
+ },
1699
+ "additionalProperties": false
1615
1700
  }
1616
1701
  },
1617
1702
  "required": [
@@ -77,7 +77,8 @@
77
77
  "action",
78
78
  "service",
79
79
  "publisher",
80
- "camera"
80
+ "camera",
81
+ "low_bandwidth"
81
82
  ]
82
83
  },
83
84
  "code": {
@@ -118,6 +118,11 @@
118
118
  0.5
119
119
  ]
120
120
  },
121
+ "low_bandwidth": {
122
+ "description": "`keep` exempts this datapoint from the low-bandwidth rate cap; its backfill still pauses.",
123
+ "type": "string",
124
+ "const": "keep"
125
+ },
121
126
  "description": {
122
127
  "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.",
123
128
  "examples": [
@@ -438,7 +443,7 @@
438
443
  }
439
444
  ]
440
445
  },
441
- "description": "Values the robot publishes, each one field of one topic or a whole topic, and **never several topics**. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
446
+ "description": "Values the robot publishes, each one field of one topic or a whole topic, and **never several topics**. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
442
447
  "defaultSnippets": [
443
448
  {
444
449
  "label": "a datapoint",
@@ -686,7 +691,7 @@
686
691
  }
687
692
  ]
688
693
  },
689
- "description": "Things the robot does on request that take time, each reported as a job with progress. **At most one job runs per action slug**: a second call is refused `busy`, and every observer of that slug watches the same job. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
694
+ "description": "Things the robot does on request that take time, each reported as a job with progress. **At most one job runs per action slug**: a second call is refused `busy`, and every observer of that slug watches the same job. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
690
695
  "defaultSnippets": [
691
696
  {
692
697
  "label": "an action",
@@ -907,7 +912,7 @@
907
912
  }
908
913
  ]
909
914
  },
910
- "description": "ROS service calls the robot answers — one request, one reply. Unlike an action a service reports **no progress** and the call returns with its result already on the job, so there is nothing left to observe; a second concurrent call is still refused `busy`, exactly as for an action. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
915
+ "description": "ROS service calls the robot answers — one request, one reply. Unlike an action a service reports **no progress** and the call returns with its result already on the job, so there is nothing left to observe; a second concurrent call is still refused `busy`, exactly as for an action. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
911
916
  "defaultSnippets": [
912
917
  {
913
918
  "label": "a service",
@@ -1215,7 +1220,7 @@
1215
1220
  }
1216
1221
  ]
1217
1222
  },
1218
- "description": "Topics clients may send to, and where the format's whole safety story lives. The `message` template fixes every value a caller cannot change, and **`failsafe` is required**: once a client falls silent the bridge sends the failsafe message itself, so an operator whose window closed does not leave a robot driving. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
1223
+ "description": "Topics clients may send to, and where the format's whole safety story lives. The `message` template fixes every value a caller cannot change, and **`failsafe` is required**: once a client falls silent the bridge sends the failsafe message itself, so an operator whose window closed does not leave a robot driving. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
1219
1224
  "defaultSnippets": [
1220
1225
  {
1221
1226
  "label": "a publisher, with its parameters and its failsafe",
@@ -1599,7 +1604,7 @@
1599
1604
  }
1600
1605
  ]
1601
1606
  },
1602
- "description": "Video the robot streams, and the still frames the cloud serves from it. `width`, `height`, `fps` and `bitrate_kbps` are what **the bridge produces before sending**, not what the camera captures — they live in the configuration rather than in a viewer's request precisely so that no viewer can make a robot send more. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state`, `robot_details` and `bridge_pressure` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all four are refused when the document is validated.",
1607
+ "description": "Video the robot streams, and the still frames the cloud serves from it. `width`, `height`, `fps` and `bitrate_kbps` are what **the bridge produces before sending**, not what the camera captures — they live in the configuration rather than in a viewer's request precisely so that no viewer can make a robot send more. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET …/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
1603
1608
  "defaultSnippets": [
1604
1609
  {
1605
1610
  "label": "a camera",
@@ -1620,6 +1625,86 @@
1620
1625
  }
1621
1626
  }
1622
1627
  ]
1628
+ },
1629
+ "low_bandwidth": {
1630
+ "description": "Overrides for the bridge's low-bandwidth mode; see the section schema.",
1631
+ "defaultSnippets": [
1632
+ {
1633
+ "label": "low-bandwidth mode, tuned",
1634
+ "description": "Enters after ten seconds above two seconds of lag, caps every datapoint to 1 Hz and lets running streams continue at a reduced bitrate.",
1635
+ "body": {
1636
+ "enter_lag_ms": 2000,
1637
+ "datapoint_max_hz": 1,
1638
+ "camera": "reduce"
1639
+ }
1640
+ }
1641
+ ],
1642
+ "type": "object",
1643
+ "properties": {
1644
+ "mode": {
1645
+ "description": "`auto` decides from the measured lag; `on` and `off` force the mode, for tests and for an operator who knows the link.",
1646
+ "enumDescriptions": [
1647
+ "The bridge enters and leaves the mode on its own, from the lag the cloud reports and the dwell it measures in its own send queue. The setting to leave alone.",
1648
+ "The mode is held on, whatever the link is doing. For a robot on a link known to be poor, and for a test that would otherwise have to wait for a real one.",
1649
+ "The mode never engages, whatever the link is doing. The robot then sends at its configured rates over a link that cannot carry them, which is a choice and not a default."
1650
+ ],
1651
+ "type": "string",
1652
+ "enum": [
1653
+ "auto",
1654
+ "on",
1655
+ "off"
1656
+ ]
1657
+ },
1658
+ "enter_lag_ms": {
1659
+ "description": "Lag or queue dwell above this enters the mode. Checked against exit_lag_ms only when both are in this document; a lone key composes with the bridge's parameter or the default on the robot, and a crossed pair is refused there when the configuration is applied, so name both when you change either.",
1660
+ "type": "integer",
1661
+ "minimum": 100,
1662
+ "maximum": 9007199254740991
1663
+ },
1664
+ "enter_after_s": {
1665
+ "description": "The entry condition must hold this long.",
1666
+ "type": "integer",
1667
+ "minimum": 1,
1668
+ "maximum": 9007199254740991
1669
+ },
1670
+ "exit_lag_ms": {
1671
+ "description": "Lag and dwell both at or below this leave the mode. Must be at or below enter_lag_ms: a crossed pair is a mode that leaves as it arrives. Checked here only when both keys are present; a lone key is checked on the robot against the parameter or default it composes with.",
1672
+ "type": "integer",
1673
+ "minimum": 0,
1674
+ "maximum": 9007199254740991
1675
+ },
1676
+ "exit_after_s": {
1677
+ "description": "The exit condition must hold this long.",
1678
+ "type": "integer",
1679
+ "minimum": 1,
1680
+ "maximum": 9007199254740991
1681
+ },
1682
+ "datapoint_max_hz": {
1683
+ "description": "The long-run rate for every datapoint in the mode, unless the datapoint says `low_bandwidth: keep`. It is an average, not a minimum gap: after a quiet spell two samples may go out close together, and over any longer window the rate holds.",
1684
+ "type": "number",
1685
+ "exclusiveMinimum": 0,
1686
+ "maximum": 20
1687
+ },
1688
+ "camera": {
1689
+ "description": "What happens to a running stream in the mode. New streams are refused either way.",
1690
+ "enumDescriptions": [
1691
+ "A running stream is re-encoded at `camera_bitrate_kbps` and keeps running. A viewer sees a worse picture rather than none.",
1692
+ "A running stream ends and the viewer is told why. The uplink is then free for datapoints, which is the right trade where video is the nice-to-have."
1693
+ ],
1694
+ "type": "string",
1695
+ "enum": [
1696
+ "reduce",
1697
+ "stop"
1698
+ ]
1699
+ },
1700
+ "camera_bitrate_kbps": {
1701
+ "description": "Bitrate applied to running streams under `reduce`.",
1702
+ "type": "integer",
1703
+ "minimum": 50,
1704
+ "maximum": 20000
1705
+ }
1706
+ },
1707
+ "additionalProperties": false
1623
1708
  }
1624
1709
  },
1625
1710
  "required": [
@@ -44,6 +44,11 @@
44
44
  0.5
45
45
  ]
46
46
  },
47
+ "low_bandwidth": {
48
+ "description": "`keep` exempts this datapoint from the low-bandwidth rate cap; its backfill still pauses.",
49
+ "type": "string",
50
+ "const": "keep"
51
+ },
47
52
  "description": {
48
53
  "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.",
49
54
  "examples": [
@@ -17,6 +17,10 @@
17
17
  "type": "integer",
18
18
  "minimum": 0,
19
19
  "maximum": 9007199254740991
20
+ },
21
+ "backfill": {
22
+ "description": "true when the sample was captured while the bridge was disconnected and is being replayed after the reconnect. The cloud keeps such a sample out of its lag measure; absent means live.",
23
+ "type": "boolean"
20
24
  }
21
25
  },
22
26
  "required": [
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "builtin": {
18
18
  "type": "boolean",
19
- "description": "`true` for the datapoints every robot has — `bridge_state`, `robot_details` and `bridge_pressure` — and `false` for everything the published configuration adds."
19
+ "description": "`true` for the datapoints every robot has — `bridge_state` and `robot_details` — and `false` for everything the published configuration adds."
20
20
  },
21
21
  "unit": {
22
22
  "anyOf": [
@@ -51,7 +51,7 @@
51
51
  ],
52
52
  "additionalProperties": false
53
53
  },
54
- "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."
54
+ "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."
55
55
  }
56
56
  },
57
57
  "required": [
@@ -27,7 +27,7 @@
27
27
  ]
28
28
  },
29
29
  "grant_types": {
30
- "description": "Accepted for conformance with RFC 7591 and then **ignored**. What comes back is what was actually granted, which §3.2.1 permits a server to substitute: `authorization_code` and nothing else, so a client that asks for `refresh_token` is registered and told plainly that it did not get one.",
30
+ "description": "Accepted for conformance with RFC 7591 and then **ignored**: both MCP authorization servers grant `authorization_code` and `refresh_token` to every registration, and the answer states what was granted (§3.2.1) rather than what was asked.",
31
31
  "type": "array",
32
32
  "items": {
33
33
  "type": "string",
@@ -28,7 +28,7 @@
28
28
  "items": {
29
29
  "type": "string"
30
30
  },
31
- "description": "The grants this client may use. Always exactly `[\"authorization_code\"]` — a client that asked for `refresh_token` is registered and told here that it did not get one, which is the substitution RFC 7591 §3.2.1 permits."
31
+ "description": "The grants this client may use. Always exactly `[\"authorization_code\", \"refresh_token\"]` — an exchange mints a refresh token and the token endpoint rotates it."
32
32
  },
33
33
  "response_types": {
34
34
  "type": "array",