@fleetless/contracts 1.0.0 → 1.0.2

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 (48) hide show
  1. package/CHANGELOG.md +68 -2
  2. package/CODE_OF_CONDUCT.md +83 -0
  3. package/CONTRIBUTING.md +136 -0
  4. package/README.md +49 -13
  5. package/SECURITY.md +55 -0
  6. package/artifacts/openapi.json +1 -1
  7. package/artifacts/routes.json +1 -1
  8. package/dist/alerts.d.ts +19 -24
  9. package/dist/alerts.js +18 -24
  10. package/dist/app-users.d.ts +7 -6
  11. package/dist/app-users.js +6 -6
  12. package/dist/apps.d.ts +21 -25
  13. package/dist/apps.js +40 -51
  14. package/dist/assets.d.ts +70 -132
  15. package/dist/assets.js +130 -223
  16. package/dist/audit.d.ts +11 -11
  17. package/dist/audit.js +25 -51
  18. package/dist/client-auth.d.ts +4 -4
  19. package/dist/client-auth.js +3 -4
  20. package/dist/common.d.ts +27 -35
  21. package/dist/common.js +26 -35
  22. package/dist/config-issues.d.ts +4 -3
  23. package/dist/config-issues.js +7 -6
  24. package/dist/config.d.ts +31 -37
  25. package/dist/config.js +81 -110
  26. package/dist/errors.d.ts +4 -3
  27. package/dist/errors.js +61 -87
  28. package/dist/identity.d.ts +18 -21
  29. package/dist/identity.js +17 -21
  30. package/dist/index.d.ts +4 -4
  31. package/dist/index.js +12 -13
  32. package/dist/introspection.d.ts +7 -6
  33. package/dist/introspection.js +6 -6
  34. package/dist/jobs.d.ts +12 -12
  35. package/dist/jobs.js +20 -25
  36. package/dist/mcp.d.ts +11 -12
  37. package/dist/mcp.js +10 -12
  38. package/dist/oauth.d.ts +13 -18
  39. package/dist/oauth.js +13 -19
  40. package/dist/protocol.d.ts +51 -62
  41. package/dist/protocol.js +107 -139
  42. package/dist/realtime.d.ts +53 -68
  43. package/dist/realtime.js +77 -103
  44. package/dist/rest.d.ts +182 -243
  45. package/dist/rest.js +301 -395
  46. package/dist/routes.d.ts +4 -3
  47. package/dist/routes.js +3 -2
  48. package/package.json +12 -7
package/dist/config.d.ts CHANGED
@@ -1,34 +1,29 @@
1
+ // SPDX-License-Identifier: Apache-2.0
1
2
  import { z } from 'zod';
2
3
  /**
3
- * What an exposed service *is*, in the developer's own words (§17).
4
+ * What an exposed service *is*, in the developer's own words.
4
5
  *
5
6
  * This is what `robot_describe` carries verbatim, so it is read by a model
6
7
  * that has never seen this robot and cannot ask a follow-up question.
7
8
  * `unit` and `range` already say what a number *is*; this says what it
8
9
  * *means*.
9
10
  *
10
- * **It lives on the configuration rather than on the app, and that was a
11
- * decision with a cost.** §17's own wording put the semantic descriptions in
12
- * the MCP app; André moved them here on 2026-08-18 so that a description is
13
- * written once per service and true for every app that reaches the robot,
14
- * beside the other metadata. What is given up is real and should not be
15
- * rediscovered as a bug: **two apps can no longer describe one service
16
- * differently for two audiences.** §17 was reworded in the same wave rather
17
- * than left contradicting this field.
11
+ * **It lives on the configuration rather than on the app, and that is a
12
+ * decision with a cost.** A description written here is written once per
13
+ * service and is true for every app that reaches the robot, beside the other
14
+ * metadata. What is given up is real and should not be rediscovered as a bug:
15
+ * **two apps cannot describe one service differently for two audiences.**
18
16
  *
19
17
  * **`.optional()` and not `.nullable().default(null)`, deliberately.** The
20
18
  * established shape in this file is a default — and every use of it has
21
19
  * added an instance to a known contradiction: `.default()` publishes the
22
20
  * field as **required** in the generated JSON Schema, because after parsing
23
- * it is always present. That is recorded four times over in
24
- * `scripts/export-schemas.ts`, whose fix (`io: 'input'`, applied per schema)
25
- * is a judgement call across roughly sixty schemas plus a re-vendor and a
26
- * re-pin in four repos. W7c's playbook said task 0 would do it; reading the
27
- * measured blast radius — 90 artifacts, 436 deletions for the blanket
28
- * version — said otherwise, at the start of a wave with five people blocked
29
- * on this pin. So the field simply does not create a fifth instance:
30
- * optional is optional in both modes, and *absent* is the single spelling of
31
- * "not described". `.min(1)` keeps the empty string from becoming a second.
21
+ * it is always present. That is recorded in `scripts/export-schemas.ts`, whose
22
+ * remedy (`io: 'input'`, applied per schema) is a judgement call across dozens
23
+ * of schemas and every consumer that vendors them. So this field simply does
24
+ * not add another instance: optional is optional in both modes, and *absent* is
25
+ * the single spelling of "not described". `.min(1)` keeps the empty string from
26
+ * becoming a second.
32
27
  */
33
28
  export declare const serviceDescription: z.ZodOptional<z.ZodString>;
34
29
  /**
@@ -134,7 +129,7 @@ export declare const parameterMap: z.ZodRecord<z.ZodString, z.ZodObject<{
134
129
  description: z.ZodOptional<z.ZodString>;
135
130
  }, z.core.$strict>>;
136
131
  /**
137
- * Slugs no configured entry may take (spec §4.3), across **all five exposure
132
+ * Slugs no configured entry may take, across **all five exposure
138
133
  * sections at once** — slugs are one namespace, so a name reserved here is
139
134
  * reserved everywhere.
140
135
  *
@@ -147,12 +142,12 @@ export declare const parameterMap: z.ZodRecord<z.ZodString, z.ZodObject<{
147
142
  * name is the honest half of that: the alternative is a slug the format
148
143
  * accepts and one route silently cannot address.
149
144
  *
150
- * **This constant is the only list.** The cloud's `validation.ts` builds its
151
- * set from it and emits `reserved_slug`; `config-store.ts` reads it for the
152
- * rename target; the console reads it for slug suggestion and repairs. Nothing
153
- * copies the members. Note that it is NOT the enumeration of built-in
154
- * datapoints — the cloud keeps that separately, and it must, now that a
155
- * reserved name exists that no plane serves.
145
+ * **This constant is the only list.** The cloud builds its set from it and
146
+ * emits `reserved_slug`; the rename path reads it for the target; an editor
147
+ * reads it for slug suggestion and repairs. Nothing copies the members. Note
148
+ * that it is NOT the enumeration of built-in datapoints — those are kept
149
+ * separately, and must be, because a reserved name exists that nothing
150
+ * publishes.
156
151
  *
157
152
  * **What it does not do: `robotConfigDoc` does not enforce it.** Reservation is
158
153
  * a semantic check that belongs with the ones that need the robot's context,
@@ -390,7 +385,7 @@ export declare function placeholderNames(node: unknown, found?: Set<string>): Se
390
385
  */
391
386
  export declare const messageMap: z.ZodRecord<z.ZodString, z.ZodUnknown>;
392
387
  /**
393
- * An action the robot can be asked to perform (spec §4.2, §11.3). At most one
388
+ * An action the robot can be asked to perform. At most one
394
389
  * job runs per action slug; a second call is refused `busy`, and every
395
390
  * observer of the slug watches the same job.
396
391
  */
@@ -426,7 +421,7 @@ export declare const actionConfig: z.ZodObject<{
426
421
  description: z.ZodOptional<z.ZodString>;
427
422
  }, z.core.$strict>;
428
423
  export type ActionConfig = z.infer<typeof actionConfig>;
429
- /** A ROS service call with validated parameters (spec §4.2). */
424
+ /** A ROS service call with validated parameters. */
430
425
  export declare const serviceConfig: z.ZodObject<{
431
426
  ros_name: z.ZodString;
432
427
  type: z.ZodString;
@@ -571,14 +566,14 @@ export type CameraSource = z.infer<typeof cameraSource>;
571
566
  */
572
567
  export declare const snapshotIntervalSeconds: z.ZodNumber;
573
568
  /**
574
- * A camera the robot exposes (spec §10).
569
+ * A camera the robot exposes.
575
570
  *
576
- * `width`/`height`/`fps`/`bitrate_kbps` are not cosmetic: §10 makes them the
571
+ * `width`/`height`/`fps`/`bitrate_kbps` are not cosmetic: they are the
577
572
  * developer's control over **the robot's own bandwidth**, which is why they
578
573
  * live in the configuration rather than in a viewer's request. A viewer never
579
574
  * gets to make a robot send more.
580
575
  *
581
- * The two modes are deliberately independent (§10):
576
+ * The two modes are deliberately independent:
582
577
  *
583
578
  * - **Snapshot** runs always, at `snapshot_interval_seconds`, whether or not
584
579
  * anyone is watching live. The cloud caches the one frame and serves every
@@ -635,7 +630,7 @@ export declare const FLEETLESS_FORMAT_VERSION = 1;
635
630
  * their own name. A duplicate name is then a YAML syntax error rather than a
636
631
  * rule somebody has to write, and the name reads as the entry's heading.
637
632
  *
638
- * Slugs remain ONE namespace across all five exposure sections (§4.1), which
633
+ * Slugs remain ONE namespace across all five exposure sections, which
639
634
  * is what lets a role grant say `{robot, slug}` without naming a kind. That
640
635
  * check spans sections and therefore lives in the cloud, not here.
641
636
  */
@@ -816,12 +811,12 @@ export declare const robotConfigDoc: z.ZodObject<{
816
811
  }, z.core.$strict>;
817
812
  export type RobotConfigDoc = z.infer<typeof robotConfigDoc>;
818
813
  /**
819
- * One thing the cloud has to say about a configuration (spec §11.5: field +
820
- * violated rule).
814
+ * One thing the cloud has to say about a configuration: the field, and the
815
+ * rule it violates.
821
816
  *
822
817
  * `error` blocks the publish. `warning` does not — an unknown topic is a
823
- * warning on purpose, because configuring a robot that has never been
824
- * connected must stay possible (spec §4.1).
818
+ * warning on purpose, because configuring a robot that has never been connected
819
+ * must stay possible.
825
820
  */
826
821
  export declare const validationIssue: z.ZodObject<{
827
822
  path: z.ZodString;
@@ -836,8 +831,7 @@ export declare const validationIssue: z.ZodObject<{
836
831
  export type ValidationIssue = z.infer<typeof validationIssue>;
837
832
  /**
838
833
  * Where a robot's configuration stands — the material for the console's
839
- * "draft newer than published", "published v2 · applied v1 · bridge offline"
840
- * (spec §15.2, robot tab 1).
834
+ * "draft newer than published", "published v2 · applied v1 · bridge offline".
841
835
  */
842
836
  export declare const configState: z.ZodObject<{
843
837
  published_version: z.ZodNullable<z.ZodNumber>;
package/dist/config.js CHANGED
@@ -10,15 +10,14 @@ import { applyError, slug, rosName, rosTypeName, fieldPath, SLUG_RULE, ROS_NAME_
10
10
  */
11
11
  import { alertSeverity } from './alerts.js';
12
12
  /**
13
- * The exposure model (spec §4): what a developer configures per robot, how a
13
+ * The exposure model: what a developer configures per robot, how a
14
14
  * configuration moves from draft to published, and how the cloud reports what
15
15
  * it refuses.
16
16
  *
17
17
  * ## What this schema decides, and what it leaves to the cloud
18
18
  *
19
- * FL-002 names thirteen validation codes and this file implements some of
20
- * them. The line was drawn four times while the format was written and never
21
- * written down, so here it is.
19
+ * The format names thirteen validation codes and this file implements some of
20
+ * them. Here is where the line runs.
22
21
  *
23
22
  * **Decided here** — everything a single entry, plus its own declared types,
24
23
  * answers on its own: `unknown_key` (every object is this file's own
@@ -67,11 +66,10 @@ import { alertSeverity } from './alerts.js';
67
66
  * `expected_range`, and no code replaced it.
68
67
  *
69
68
  * `robotConfigDoc` carries all six sections — messages, datapoints, actions,
70
- * services, publishers and cameras — plus, since FL-002, the alerts, the
71
- * chart bounds and the camera credentials that used to live outside it.
72
- * Everything configurable about a robot is in this document, and there is one
73
- * door to it. FL-002 rewrote the slug grammar (underscores, not dashes) and
74
- * keyed every section by name; draft/publish and versioning are unchanged.
69
+ * services, publishers and cameras — plus the alerts, the chart bounds and the
70
+ * camera credentials. Everything configurable about a robot is in this
71
+ * document, and there is one door to it. Every section is keyed by name, and
72
+ * the document moves from draft to published under a version.
75
73
  */
76
74
  /**
77
75
  * What every `pattern` in this document means, said in words.
@@ -121,23 +119,18 @@ const DEVICE_PATH_RULE = 'A capture device is a path under `/dev/`, and the char
121
119
  *
122
120
  * **`slug` itself stays plain in `common.ts`, and the reason is blast radius.**
123
121
  * Not metadata loss: `.meta()` on a clone *merges* with the parent's entry per
124
- * key and resolves it lazily, measured against zod 4.4.3 and written up at
125
- * `messageBody`'s own `.meta()` below — a later `description` on a use of
126
- * `slug` would keep the sentence, not drop it.
127
- *
128
- * What that reach would cost was measured instead, by adding the one `.meta()`
129
- * line to `slug` in a copy of `src/` and re-exporting every artifact under each
130
- * schema's own `io`: **42 of the 159 published schema artifacts** would carry
131
- * it, `bridge-hello`, `datapoint-frame`, `snapshot-header` and
132
- * `bridge-camera-state` among them — protocol frames the bridge **vendors**
133
- * under `bridge/test/contracts/schema/`, so rewording one sentence would become
134
- * a re-vendor plus a `SOURCE.md` edit in another repo. As landed the same
135
- * search finds **7**, all config-derived.
136
- *
137
- * Whether `vscode-json-languageservice` honours `patternErrorMessage` on a
138
- * `propertyNames` schema at all is **not measured** — §1.3 measured a value
139
- * position, not a key one. It ships for the same reason as the rest: the
140
- * artifact is read by tools and by people.
122
+ * key and resolves it lazily — a later `description` on a use of `slug` keeps
123
+ * the sentence rather than dropping it.
124
+ *
125
+ * The cost is reach. Annotating `slug` itself puts the sentence into dozens of
126
+ * the published schema artifacts, protocol frames included, so rewording one
127
+ * sentence becomes a change to every consumer that vendors those. Annotating
128
+ * the key here reaches only the configuration schemas, which is where the
129
+ * sentence is useful.
130
+ *
131
+ * Whether a given YAML language service honours `patternErrorMessage` on a
132
+ * `propertyNames` schema is not something this package can promise. It ships
133
+ * for the same reason as the rest: the artifact is read by tools and by people.
141
134
  */
142
135
  const mapKey = slug.meta({ patternErrorMessage: SLUG_RULE });
143
136
  /**
@@ -256,37 +249,28 @@ const namesItsAbsence = (shape) => Object.fromEntries(Object.entries(shape).map(
256
249
  * its own name when it is absent.
257
250
  *
258
251
  * **This is not `z.strictObject`** — it is this file's, wrapping it. The
259
- * difference is the second half: `Invalid input: expected object, received
260
- * undefined` was the whole of what a developer was told when a camera had no
261
- * `source:` (design §1.5), naming neither the key nor the fact that it was
262
- * required. monaco-yaml said `Missing property "source".` for the same
263
- * document, and was right to.
252
+ * difference is the second half. Plain zod tells a developer whose camera has
253
+ * no `source:` only `Invalid input: expected object, received undefined`,
254
+ * naming neither the key nor the fact that it was required. A YAML language
255
+ * service says `Missing property "source".` for the same document, and is right
256
+ * to.
264
257
  *
265
258
  * It has to be done a field at a time. zod attributes a missing key to the
266
259
  * **field's own** schema — an `invalid_type` whose input is `undefined` — and
267
- * an `error` on the containing object is never consulted for it; measured on
268
- * zod 4.4.3, an error map on the object saw no such issue at all. So the
260
+ * an `error` on the containing object is never consulted for it. So the
269
261
  * sentence is attached to every field of every shape, here, in one place,
270
- * rather than at the eighteen objects and hundred-odd fields it would
271
- * otherwise have to be remembered at.
272
- *
273
- * **How "every" is enforced, because the first version of this comment said
274
- * "every" and was wrong.** Six of the eighteen objects were written
275
- * `z\n .strictObject({`, so `z.strictObject` never appeared on one line and a
276
- * `grep` for it returned only prose. Twelve conversions read as eighteen, and
277
- * eleven required keys — `datapoints.<slug>.topic` and `.type` among them,
278
- * which is the commonest entry in the whole format — went on reciting the
279
- * sentence §1.5 calls unusable. The claim was in the source, which is what the
280
- * next person reads.
281
- *
282
- * What makes it true now is not this paragraph. It is
262
+ * rather than at each of the objects and fields it would otherwise have to be
263
+ * remembered at.
264
+ *
265
+ * **How "every" is enforced.** Not by this paragraph: a claim in a comment is
266
+ * what the next person reads and not what holds. It is
283
267
  * `config-zod-messages.test.ts`'s *"a required key that is absent names
284
- * itself"*: a walk of the exported schema's `required` arrays against a
268
+ * itself"* — a walk of the exported schema's `required` arrays against a
285
269
  * fully-populated document, `oneOf` branches resolved by their discriminator,
286
- * deleting one key at a time and asserting the message names it. It reaches 52
287
- * positions across 19 objects, and the count comes out of the walk rather than
288
- * off a list — a required key added to the format later is swept the day it
289
- * exists, and an object that skips this helper is red before it is merged.
270
+ * deleting one key at a time and asserting the message names it. The count
271
+ * comes out of the walk rather than off a list, so a required key added to the
272
+ * format later is swept the day it exists, and an object that skips this helper
273
+ * is red before it is merged.
290
274
  */
291
275
  const strictObject = (shape) => z.strictObject(namesItsAbsence(shape));
292
276
  /**
@@ -384,35 +368,29 @@ const param = (name) => `\\\${${name}}`;
384
368
  */
385
369
  const underSlug = (slugKey, snippet) => ({ ...snippet, body: { [slugKey]: snippet.body } });
386
370
  /**
387
- * What an exposed service *is*, in the developer's own words (§17).
371
+ * What an exposed service *is*, in the developer's own words.
388
372
  *
389
373
  * This is what `robot_describe` carries verbatim, so it is read by a model
390
374
  * that has never seen this robot and cannot ask a follow-up question.
391
375
  * `unit` and `range` already say what a number *is*; this says what it
392
376
  * *means*.
393
377
  *
394
- * **It lives on the configuration rather than on the app, and that was a
395
- * decision with a cost.** §17's own wording put the semantic descriptions in
396
- * the MCP app; André moved them here on 2026-08-18 so that a description is
397
- * written once per service and true for every app that reaches the robot,
398
- * beside the other metadata. What is given up is real and should not be
399
- * rediscovered as a bug: **two apps can no longer describe one service
400
- * differently for two audiences.** §17 was reworded in the same wave rather
401
- * than left contradicting this field.
378
+ * **It lives on the configuration rather than on the app, and that is a
379
+ * decision with a cost.** A description written here is written once per
380
+ * service and is true for every app that reaches the robot, beside the other
381
+ * metadata. What is given up is real and should not be rediscovered as a bug:
382
+ * **two apps cannot describe one service differently for two audiences.**
402
383
  *
403
384
  * **`.optional()` and not `.nullable().default(null)`, deliberately.** The
404
385
  * established shape in this file is a default — and every use of it has
405
386
  * added an instance to a known contradiction: `.default()` publishes the
406
387
  * field as **required** in the generated JSON Schema, because after parsing
407
- * it is always present. That is recorded four times over in
408
- * `scripts/export-schemas.ts`, whose fix (`io: 'input'`, applied per schema)
409
- * is a judgement call across roughly sixty schemas plus a re-vendor and a
410
- * re-pin in four repos. W7c's playbook said task 0 would do it; reading the
411
- * measured blast radius — 90 artifacts, 436 deletions for the blanket
412
- * version — said otherwise, at the start of a wave with five people blocked
413
- * on this pin. So the field simply does not create a fifth instance:
414
- * optional is optional in both modes, and *absent* is the single spelling of
415
- * "not described". `.min(1)` keeps the empty string from becoming a second.
388
+ * it is always present. That is recorded in `scripts/export-schemas.ts`, whose
389
+ * remedy (`io: 'input'`, applied per schema) is a judgement call across dozens
390
+ * of schemas and every consumer that vendors them. So this field simply does
391
+ * not add another instance: optional is optional in both modes, and *absent* is
392
+ * the single spelling of "not described". `.min(1)` keeps the empty string from
393
+ * becoming a second.
416
394
  */
417
395
  export const serviceDescription = z.string().min(1).max(2000).optional();
418
396
  /**
@@ -673,7 +651,7 @@ export const parameterMap = slugKeyed(parameterSpec)
673
651
  defaultSnippets: [underSlug('${1:speed}', PARAMETER_SNIPPET)],
674
652
  });
675
653
  /**
676
- * Slugs no configured entry may take (spec §4.3), across **all five exposure
654
+ * Slugs no configured entry may take, across **all five exposure
677
655
  * sections at once** — slugs are one namespace, so a name reserved here is
678
656
  * reserved everywhere.
679
657
  *
@@ -686,12 +664,12 @@ export const parameterMap = slugKeyed(parameterSpec)
686
664
  * name is the honest half of that: the alternative is a slug the format
687
665
  * accepts and one route silently cannot address.
688
666
  *
689
- * **This constant is the only list.** The cloud's `validation.ts` builds its
690
- * set from it and emits `reserved_slug`; `config-store.ts` reads it for the
691
- * rename target; the console reads it for slug suggestion and repairs. Nothing
692
- * copies the members. Note that it is NOT the enumeration of built-in
693
- * datapoints — the cloud keeps that separately, and it must, now that a
694
- * reserved name exists that no plane serves.
667
+ * **This constant is the only list.** The cloud builds its set from it and
668
+ * emits `reserved_slug`; the rename path reads it for the target; an editor
669
+ * reads it for slug suggestion and repairs. Nothing copies the members. Note
670
+ * that it is NOT the enumeration of built-in datapoints — those are kept
671
+ * separately, and must be, because a reserved name exists that nothing
672
+ * publishes.
695
673
  *
696
674
  * **What it does not do: `robotConfigDoc` does not enforce it.** Reservation is
697
675
  * a semantic check that belongs with the ones that need the robot's context,
@@ -1366,7 +1344,7 @@ const ACTION_SNIPPET = {
1366
1344
  },
1367
1345
  };
1368
1346
  /**
1369
- * An action the robot can be asked to perform (spec §4.2, §11.3). At most one
1347
+ * An action the robot can be asked to perform. At most one
1370
1348
  * job runs per action slug; a second call is refused `busy`, and every
1371
1349
  * observer of the slug watches the same job.
1372
1350
  */
@@ -1406,7 +1384,7 @@ const SERVICE_SNIPPET = {
1406
1384
  description: '${4:Resets odometry to the origin.}',
1407
1385
  },
1408
1386
  };
1409
- /** A ROS service call with validated parameters (spec §4.2). */
1387
+ /** A ROS service call with validated parameters. */
1410
1388
  export const serviceConfig = strictObject({
1411
1389
  ros_name: rosName.meta({
1412
1390
  description: 'The ROS service the robot answers on, as an absolute graph name. The call is one request and one reply with no progress in between, so whatever this service does has to finish inside that reply; anything long-running belongs in `actions`.',
@@ -1612,7 +1590,7 @@ export const cameraCredentials = strictObject({
1612
1590
  }],
1613
1591
  });
1614
1592
  /**
1615
- * Where a camera's frames come from (spec §10 names four sources).
1593
+ * Where a camera's frames come from — one of four sources.
1616
1594
  *
1617
1595
  * A discriminated union rather than optional fields, so an impossible camera
1618
1596
  * is **unrepresentable** rather than merely invalid — there is no way to
@@ -1686,16 +1664,12 @@ export const cameraSource = z.discriminatedUnion('kind', [
1686
1664
  description: 'Selects the RTSP source: this camera then carries `url`, and optionally `transport` and `credentials`.',
1687
1665
  }),
1688
1666
  /**
1689
- * Scheme-constrained deliberately. The playbook drafted `z.string().url()`
1690
- * here and the shipped contract was `z.string().min(1).max(2048)` — nobody
1691
- * recorded the change, and the W6 review found the consequence: the bridge
1692
- * opens these with libraries that honour `file:` and `ftp:`, so an
1693
- * unconstrained URL turns a configuration document into an arbitrary
1694
- * local-file read on the robot, with the two distinct failure codes
1695
- * doubling as a file-existence oracle. Spec §7.6 is ROS-pure exposure with
1696
- * no shell or http features; that rule came back by omission rather than
1697
- * by intent. The bridge re-checks this too — a robot must not become a
1698
- * file server because a validator changed.
1667
+ * Scheme-constrained deliberately. The bridge opens these with libraries
1668
+ * that honour `file:` and `ftp:`, so an unconstrained URL turns a
1669
+ * configuration document into an arbitrary local-file read on the robot,
1670
+ * with the two distinct failure codes doubling as a file-existence oracle.
1671
+ * The bridge re-checks this too — a robot must not become a file server
1672
+ * because a validator changed.
1699
1673
  */
1700
1674
  url: z
1701
1675
  .string()
@@ -1743,8 +1717,8 @@ export const cameraSource = z.discriminatedUnion('kind', [
1743
1717
  /**
1744
1718
  * The host and the path this branch's own snippet body inserts, and the
1745
1719
  * URL its rule sentence names — one answer to "what goes here?", not a
1746
- * third. The sibling `rtsp` url had an `examples` from the first day and
1747
- * this position was the format's only silent URL (§1.1).
1720
+ * third. Every URL position in the format carries an example; a silent
1721
+ * one is the position a developer has to guess at.
1748
1722
  */
1749
1723
  examples: ['http://cam-1.plant.local/video.mjpg'],
1750
1724
  }),
@@ -1769,16 +1743,14 @@ export const cameraSource = z.discriminatedUnion('kind', [
1769
1743
  * on the robot, never by the cloud.
1770
1744
  *
1771
1745
  * Constrained to `/dev/` for the same reason the `rtsp` and `mjpeg` URLs
1772
- * are constrained to their schemes, and it was missed the first time
1773
- * (Momus, W6 verification). The device string reaches
1746
+ * are constrained to their schemes. The device string reaches
1774
1747
  * `cv2.VideoCapture(device)` on the robot, and OpenCV does not restrict
1775
- * itself to devices: measured on cv2 4.5.4, an ordinary local video file
1776
- * opens and its pixels are published to the cloud, and so does
1777
- * `http://127.0.0.1:8899/secret.jpg`. Unconstrained, this field is an
1778
- * arbitrary local-file read *and* an outbound fetch from inside the robot
1779
- * — the §7.6 violation closed for the other two source kinds, reachable
1780
- * through the fourth, because "it is just a device path" read like a
1781
- * reason not to check.
1748
+ * itself to devices: an ordinary local video file opens and its pixels are
1749
+ * published to the cloud, and so does an `http://` URL pointing back inside
1750
+ * the robot's own network. Unconstrained, this field is an arbitrary
1751
+ * local-file read *and* an outbound fetch from inside the robot — the same
1752
+ * hole closed for the other two source kinds, reachable through the fourth,
1753
+ * because "it is just a device path" reads like a reason not to check.
1782
1754
  *
1783
1755
  * Narrower than the URL hole in one respect worth recording: a non-media
1784
1756
  * file and a missing file both fail to open, so this branch never worked
@@ -1851,14 +1823,14 @@ const CAMERA_SNIPPET = {
1851
1823
  },
1852
1824
  };
1853
1825
  /**
1854
- * A camera the robot exposes (spec §10).
1826
+ * A camera the robot exposes.
1855
1827
  *
1856
- * `width`/`height`/`fps`/`bitrate_kbps` are not cosmetic: §10 makes them the
1828
+ * `width`/`height`/`fps`/`bitrate_kbps` are not cosmetic: they are the
1857
1829
  * developer's control over **the robot's own bandwidth**, which is why they
1858
1830
  * live in the configuration rather than in a viewer's request. A viewer never
1859
1831
  * gets to make a robot send more.
1860
1832
  *
1861
- * The two modes are deliberately independent (§10):
1833
+ * The two modes are deliberately independent:
1862
1834
  *
1863
1835
  * - **Snapshot** runs always, at `snapshot_interval_seconds`, whether or not
1864
1836
  * anyone is watching live. The cloud caches the one frame and serves every
@@ -1913,7 +1885,7 @@ const capped = (entry, max, what) => slugKeyed(entry).refine((m) => Object.keys(
1913
1885
  * their own name. A duplicate name is then a YAML syntax error rather than a
1914
1886
  * rule somebody has to write, and the name reads as the entry's heading.
1915
1887
  *
1916
- * Slugs remain ONE namespace across all five exposure sections (§4.1), which
1888
+ * Slugs remain ONE namespace across all five exposure sections, which
1917
1889
  * is what lets a role grant say `{robot, slug}` without naming a kind. That
1918
1890
  * check spans sections and therefore lives in the cloud, not here.
1919
1891
  */
@@ -1950,12 +1922,12 @@ export const robotConfigDoc = strictObject({
1950
1922
  }).optional(),
1951
1923
  });
1952
1924
  /**
1953
- * One thing the cloud has to say about a configuration (spec §11.5: field +
1954
- * violated rule).
1925
+ * One thing the cloud has to say about a configuration: the field, and the
1926
+ * rule it violates.
1955
1927
  *
1956
1928
  * `error` blocks the publish. `warning` does not — an unknown topic is a
1957
- * warning on purpose, because configuring a robot that has never been
1958
- * connected must stay possible (spec §4.1).
1929
+ * warning on purpose, because configuring a robot that has never been connected
1930
+ * must stay possible.
1959
1931
  */
1960
1932
  export const validationIssue = z.object({
1961
1933
  path: z.string().min(1),
@@ -1966,8 +1938,7 @@ export const validationIssue = z.object({
1966
1938
  });
1967
1939
  /**
1968
1940
  * Where a robot's configuration stands — the material for the console's
1969
- * "draft newer than published", "published v2 · applied v1 · bridge offline"
1970
- * (spec §15.2, robot tab 1).
1941
+ * "draft newer than published", "published v2 · applied v1 · bridge offline".
1971
1942
  */
1972
1943
  export const configState = z.object({
1973
1944
  published_version: z.number().int().positive().nullable(),
package/dist/errors.d.ts CHANGED
@@ -1,6 +1,7 @@
1
+ // SPDX-License-Identifier: Apache-2.0
1
2
  import { z } from 'zod';
2
3
  /**
3
- * The one error shape of the REST and realtime APIs (spec §11.5): a stable
4
+ * The one error shape of the REST and realtime APIs: a stable
4
5
  * machine-readable code plus a human message; validation errors name the
5
6
  * field and the violated rule in `details`.
6
7
  */
@@ -11,7 +12,7 @@ export declare const apiError: z.ZodObject<{
11
12
  }, z.core.$strip>;
12
13
  export type ApiError = z.infer<typeof apiError>;
13
14
  /**
14
- * One violated §4.4 rule. `details` on the envelope stays `unknown` — codes
15
+ * One violated parameter rule. `details` on the envelope stays `unknown` — codes
15
16
  * are an open set, so their payloads cannot all be enumerated — but the
16
17
  * payload of `parameter_invalid` **is** pinned here, because otherwise every
17
18
  * consumer guesses: the cloud emits one shape, the SDK sniffs for two, the
@@ -44,7 +45,7 @@ export declare const parameterInvalidDetails: z.ZodObject<{
44
45
  }, z.core.$strip>;
45
46
  export type ParameterInvalidDetails = z.infer<typeof parameterInvalidDetails>;
46
47
  /**
47
- * The codes in use as of W2. The wire deliberately allows any string — this
48
+ * The codes in use today. The wire deliberately allows any string — this
48
49
  * list is the shared vocabulary, not a closed set, so a new refusal never
49
50
  * needs a contracts release before it can be reported honestly.
50
51
  */