@fleetless/contracts 1.0.0 → 1.0.3

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 (49) hide show
  1. package/CHANGELOG.md +97 -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 +11 -11
  7. package/artifacts/routes.json +12 -12
  8. package/artifacts/schema/create-app-oidc-provider-request.schema.json +1 -1
  9. package/dist/alerts.d.ts +23 -28
  10. package/dist/alerts.js +23 -29
  11. package/dist/app-users.d.ts +18 -19
  12. package/dist/app-users.js +18 -20
  13. package/dist/apps.d.ts +21 -25
  14. package/dist/apps.js +42 -52
  15. package/dist/assets.d.ts +70 -132
  16. package/dist/assets.js +130 -223
  17. package/dist/audit.d.ts +14 -15
  18. package/dist/audit.js +28 -55
  19. package/dist/client-auth.d.ts +9 -9
  20. package/dist/client-auth.js +8 -9
  21. package/dist/common.d.ts +29 -37
  22. package/dist/common.js +28 -37
  23. package/dist/config-issues.d.ts +23 -25
  24. package/dist/config-issues.js +17 -17
  25. package/dist/config.d.ts +37 -44
  26. package/dist/config.js +145 -187
  27. package/dist/errors.d.ts +4 -3
  28. package/dist/errors.js +83 -116
  29. package/dist/identity.d.ts +24 -27
  30. package/dist/identity.js +23 -27
  31. package/dist/index.d.ts +4 -4
  32. package/dist/index.js +14 -15
  33. package/dist/introspection.d.ts +7 -6
  34. package/dist/introspection.js +6 -6
  35. package/dist/jobs.d.ts +16 -16
  36. package/dist/jobs.js +24 -29
  37. package/dist/mcp.d.ts +14 -15
  38. package/dist/mcp.js +12 -14
  39. package/dist/oauth.d.ts +21 -27
  40. package/dist/oauth.js +33 -43
  41. package/dist/protocol.d.ts +51 -62
  42. package/dist/protocol.js +107 -139
  43. package/dist/realtime.d.ts +53 -68
  44. package/dist/realtime.js +78 -104
  45. package/dist/rest.d.ts +183 -244
  46. package/dist/rest.js +305 -399
  47. package/dist/routes.d.ts +4 -3
  48. package/dist/routes.js +33 -32
  49. package/package.json +12 -7
@@ -1,19 +1,18 @@
1
+ // SPDX-License-Identifier: Apache-2.0
1
2
  import { z } from 'zod';
2
3
  import type { ValidationIssue } from './config.js';
3
4
  /**
4
5
  * What is wrong with a configuration document, in one account.
5
6
  *
6
- * Everything here used to live in `cloud/src/validation.ts`. It is in
7
- * contracts because the console has to say **exactly** what the server says
8
- * about a document — same codes, same sentences, same paths — and the only
9
- * way that is true is if it is the same code. A console that reimplemented
10
- * this and then disagreed with the server about what is wrong would be worse
11
- * than a console that said nothing (spec D3).
12
- *
13
- * **The second door D3 forbids existed for one wave, and closed in wave 2
14
- * task 8** (cloud `a307e18`, 2026-09-03). The cloud cannot import a specifier
15
- * it has not pinned, so its own copy of `schemaIssues`, `refusal`, `slugOf`,
16
- * `formatPath` and `valueAt` stood from wave 1, when this module landed here,
7
+ * It lives in contracts because an editor has to say **exactly** what the
8
+ * server says about a document — same codes, same sentences, same paths — and
9
+ * the only way that is true is if it is the same code. An editor that
10
+ * reimplemented this and then disagreed with the server about what is wrong
11
+ * would be worse than one that said nothing.
12
+ *
13
+ * **A second implementation is the thing this module exists to prevent.** A
14
+ * server that cannot import this package keeps its own copy of `schemaIssues`,
15
+ * `refusal`, `slugOf`, `formatPath` and `valueAt`,
17
16
  * until that re-pin deleted them and imported these. The window is recorded
18
17
  * rather than dropped because it cost a live bug while it was open: the
19
18
  * cloud's own `formatPath` wrote a blank path segment as the empty string,
@@ -41,17 +40,16 @@ export declare const DOCUMENT_ROOT_PATH = "(document)";
41
40
  * `messages:` is deliberately not among them: its names are their own
42
41
  * namespace.
43
42
  *
44
- * `cloud/src/config-sections.ts` re-exports this constant and drives the
45
- * cloud's iteration over sections from it; the console reads it directly
46
- * (`useConfigRepairs.ts`). It was spelled out separately in all three until
47
- * wave 2 task 8 (cloud `a307e18`, 2026-09-03) — this is the only spelling
43
+ * The cloud re-exports this constant and drives its iteration over sections
44
+ * from it; an editor reads it directly. Spelling it out separately in each
45
+ * would be three copies — this is the only spelling
48
46
  * since.
49
47
  */
50
48
  export declare const EXPOSURE_SECTIONS: readonly ["datapoints", "actions", "services", "publishers", "cameras"];
51
49
  export type ExposureSection = (typeof EXPOSURE_SECTIONS)[number];
52
50
  /**
53
51
  * The refusals `robotConfigDoc` already made, reported as validation issues
54
- * with their FL-002 codes.
52
+ * with the format's own codes.
55
53
  *
56
54
  * **This maps; it does not re-decide.** Seven of the thirteen codes are
57
55
  * answered by the schema before a document ever becomes a `RobotConfigDoc`,
@@ -72,8 +70,8 @@ export type ExposureSection = (typeof EXPOSURE_SECTIONS)[number];
72
70
  * sentence differs at the document root, where there is no key to remove:
73
71
  * see `EMPTY_DOCUMENT_MESSAGE`.
74
72
  *
75
- * Everything else keeps zod's own code. Those are refusals with no FL-002
76
- * code — a reversed `min_value`/`max_value` pair, a section over its cap, a
73
+ * Everything else keeps zod's own code. Those are refusals the format names no
74
+ * code for — a reversed `min_value`/`max_value` pair, a section over its cap, a
77
75
  * key that is not a slug — and inventing a fourteenth code for them would put
78
76
  * a code on the wire that no table documents.
79
77
  */
@@ -89,10 +87,10 @@ export declare function schemaIssues(value: unknown, issues: readonly SchemaIssu
89
87
  * width. Rendered bare, such a key produced a path a reader cannot act
90
88
  * on — and at the root it produced the empty string, which
91
89
  * `validationIssue.path` (`z.string().min(1)`) refuses. That was the cloud
92
- * publishing a finding that fails the cloud's own contract for findings, and
93
- * after D2 stored the draft it cost the whole `configDraftResponse`, not one
94
- * issue: the console's `safeParse` dropped the response and handed the editor
95
- * nothing, for two characters typed.
90
+ * publishing a finding that fails its own contract for findings, and because a
91
+ * draft that does not parse is stored rather than refused it costs the whole
92
+ * `configDraftResponse`, not one issue: a client's `safeParse` drops the
93
+ * response and hands the editor nothing, for two characters typed.
96
94
  *
97
95
  * The quoted spelling is the segment's JSON string literal, and that is the
98
96
  * whole of the reason for choosing it: JSON's string syntax is a subset of
@@ -125,9 +123,9 @@ export declare function formatPath(path: readonly PropertyKey[]): string;
125
123
  *
126
124
  * Escaping on the way out was the alternative and was rejected: `path` is a
127
125
  * wire field (`validationIssue.path`), it is rendered to developers as-is,
128
- * and every recorded expectation in this repo and the cloud's spells it
129
- * unescaped. Changing what the server says about every document to make one
130
- * console lookup total is the larger of the two costs.
126
+ * and every recorded expectation on both sides spells it unescaped. Changing
127
+ * what the server says about every document to make one client-side lookup
128
+ * total is the larger of the two costs.
131
129
  *
132
130
  * So the property this has, and the one its test asserts, is the narrow one:
133
131
  * **a path round-trips when no string segment contains `.` or `[`, and the
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: Apache-2.0
1
2
  /** What a path with no segments at all is called, since `path` may not be empty. */
2
3
  export const DOCUMENT_ROOT_PATH = '(document)';
3
4
  /**
@@ -6,16 +7,15 @@ export const DOCUMENT_ROOT_PATH = '(document)';
6
7
  * `messages:` is deliberately not among them: its names are their own
7
8
  * namespace.
8
9
  *
9
- * `cloud/src/config-sections.ts` re-exports this constant and drives the
10
- * cloud's iteration over sections from it; the console reads it directly
11
- * (`useConfigRepairs.ts`). It was spelled out separately in all three until
12
- * wave 2 task 8 (cloud `a307e18`, 2026-09-03) — this is the only spelling
10
+ * The cloud re-exports this constant and drives its iteration over sections
11
+ * from it; an editor reads it directly. Spelling it out separately in each
12
+ * would be three copies — this is the only spelling
13
13
  * since.
14
14
  */
15
15
  export const EXPOSURE_SECTIONS = ['datapoints', 'actions', 'services', 'publishers', 'cameras'];
16
16
  /**
17
17
  * The refusals `robotConfigDoc` already made, reported as validation issues
18
- * with their FL-002 codes.
18
+ * with the format's own codes.
19
19
  *
20
20
  * **This maps; it does not re-decide.** Seven of the thirteen codes are
21
21
  * answered by the schema before a document ever becomes a `RobotConfigDoc`,
@@ -36,8 +36,8 @@ export const EXPOSURE_SECTIONS = ['datapoints', 'actions', 'services', 'publishe
36
36
  * sentence differs at the document root, where there is no key to remove:
37
37
  * see `EMPTY_DOCUMENT_MESSAGE`.
38
38
  *
39
- * Everything else keeps zod's own code. Those are refusals with no FL-002
40
- * code — a reversed `min_value`/`max_value` pair, a section over its cap, a
39
+ * Everything else keeps zod's own code. Those are refusals the format names no
40
+ * code for — a reversed `min_value`/`max_value` pair, a section over its cap, a
41
41
  * key that is not a slug — and inventing a fourteenth code for them would put
42
42
  * a code on the wire that no table documents.
43
43
  */
@@ -69,9 +69,9 @@ const NULL_KEY_MESSAGE = 'This key is null. Omission is the only spelling of "no
69
69
  * a genuine `invalid_type` on `null` at the empty path, so the code is right —
70
70
  * but the sentence for a null *key* told the developer to remove a key that
71
71
  * does not exist, and "select all, delete" is the commonest way anybody gets
72
- * here. Since FL-005 D2 stores the draft rather than refusing it, that
73
- * sentence is what the FINDINGS panel shows persistently for an emptied
74
- * editor, where it used to ride a one-shot 422 nobody read.
72
+ * here. A draft that does not parse is stored rather than refused, so this
73
+ * sentence is what an editor's findings panel shows persistently for an emptied
74
+ * document rather than a one-shot refusal nobody reads.
75
75
  *
76
76
  * It names the smallest legal document rather than only saying what is wrong,
77
77
  * because at this path there is no line to jump to and no repair to offer —
@@ -106,10 +106,10 @@ function slugOf(path) {
106
106
  * width. Rendered bare, such a key produced a path a reader cannot act
107
107
  * on — and at the root it produced the empty string, which
108
108
  * `validationIssue.path` (`z.string().min(1)`) refuses. That was the cloud
109
- * publishing a finding that fails the cloud's own contract for findings, and
110
- * after D2 stored the draft it cost the whole `configDraftResponse`, not one
111
- * issue: the console's `safeParse` dropped the response and handed the editor
112
- * nothing, for two characters typed.
109
+ * publishing a finding that fails its own contract for findings, and because a
110
+ * draft that does not parse is stored rather than refused it costs the whole
111
+ * `configDraftResponse`, not one issue: a client's `safeParse` drops the
112
+ * response and hands the editor nothing, for two characters typed.
113
113
  *
114
114
  * The quoted spelling is the segment's JSON string literal, and that is the
115
115
  * whole of the reason for choosing it: JSON's string syntax is a subset of
@@ -198,9 +198,9 @@ function isBlank(segment) {
198
198
  *
199
199
  * Escaping on the way out was the alternative and was rejected: `path` is a
200
200
  * wire field (`validationIssue.path`), it is rendered to developers as-is,
201
- * and every recorded expectation in this repo and the cloud's spells it
202
- * unescaped. Changing what the server says about every document to make one
203
- * console lookup total is the larger of the two costs.
201
+ * and every recorded expectation on both sides spells it unescaped. Changing
202
+ * what the server says about every document to make one client-side lookup
203
+ * total is the larger of the two costs.
204
204
  *
205
205
  * So the property this has, and the one its test asserts, is the narrow one:
206
206
  * **a path round-trips when no string segment contains `.` or `[`, and the
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,
@@ -367,10 +362,9 @@ export declare const messageBody: z.ZodUnknown;
367
362
  * contract.** This runs inside `publisherConfig`'s failsafe refinement, so a
368
363
  * `RangeError: Maximum call stack size exceeded` did not stay here: it
369
364
  * propagated out of `safeParse`, which is specified to return a result and
370
- * not to throw. Measured on the recursive version — fine at 8 000 levels of
371
- * nesting, throwing at 20 000 — and a flow-style YAML one-liner reaches that
372
- * in about 120 KB of input. A draft PUT would have answered 500 where it
373
- * meant 400.
365
+ * not to throw. A recursive walk survives a few thousand levels of nesting and
366
+ * throws somewhere above that, which a flow-style YAML one-liner reaches in
367
+ * about 120 KB of input — so a draft PUT would answer 500 where it meant 400.
374
368
  *
375
369
  * `seen` is not an optimisation. YAML anchors can express a cycle
376
370
  * (`&a { b: *a }`), and the parser resolves an alias to the same object, so
@@ -390,7 +384,7 @@ export declare function placeholderNames(node: unknown, found?: Set<string>): Se
390
384
  */
391
385
  export declare const messageMap: z.ZodRecord<z.ZodString, z.ZodUnknown>;
392
386
  /**
393
- * An action the robot can be asked to perform (spec §4.2, §11.3). At most one
387
+ * An action the robot can be asked to perform. At most one
394
388
  * job runs per action slug; a second call is refused `busy`, and every
395
389
  * observer of the slug watches the same job.
396
390
  */
@@ -426,7 +420,7 @@ export declare const actionConfig: z.ZodObject<{
426
420
  description: z.ZodOptional<z.ZodString>;
427
421
  }, z.core.$strict>;
428
422
  export type ActionConfig = z.infer<typeof actionConfig>;
429
- /** A ROS service call with validated parameters (spec §4.2). */
423
+ /** A ROS service call with validated parameters. */
430
424
  export declare const serviceConfig: z.ZodObject<{
431
425
  ros_name: z.ZodString;
432
426
  type: z.ZodString;
@@ -565,20 +559,20 @@ export type CameraSource = z.infer<typeof cameraSource>;
565
559
  *
566
560
  * The bound lives here once, and `rest.ts`'s `cameraDescriptor` reuses it —
567
561
  * the same treatment `rateThrottleHz` got, and for the same reason: the
568
- * descriptor used to say `snapshot_interval_ms` while the document said
569
- * seconds, so the cloud converted on one descriptor and not its sibling, with
570
- * nothing in either file saying so.
562
+ * two spellings of one interval — milliseconds in a descriptor, seconds in the
563
+ * document — mean a server converting on one and not the other, with nothing in
564
+ * either file saying so.
571
565
  */
572
566
  export declare const snapshotIntervalSeconds: z.ZodNumber;
573
567
  /**
574
- * A camera the robot exposes (spec §10).
568
+ * A camera the robot exposes.
575
569
  *
576
- * `width`/`height`/`fps`/`bitrate_kbps` are not cosmetic: §10 makes them the
570
+ * `width`/`height`/`fps`/`bitrate_kbps` are not cosmetic: they are the
577
571
  * developer's control over **the robot's own bandwidth**, which is why they
578
572
  * live in the configuration rather than in a viewer's request. A viewer never
579
573
  * gets to make a robot send more.
580
574
  *
581
- * The two modes are deliberately independent (§10):
575
+ * The two modes are deliberately independent:
582
576
  *
583
577
  * - **Snapshot** runs always, at `snapshot_interval_seconds`, whether or not
584
578
  * anyone is watching live. The cloud caches the one frame and serves every
@@ -635,7 +629,7 @@ export declare const FLEETLESS_FORMAT_VERSION = 1;
635
629
  * their own name. A duplicate name is then a YAML syntax error rather than a
636
630
  * rule somebody has to write, and the name reads as the entry's heading.
637
631
  *
638
- * Slugs remain ONE namespace across all five exposure sections (§4.1), which
632
+ * Slugs remain ONE namespace across all five exposure sections, which
639
633
  * is what lets a role grant say `{robot, slug}` without naming a kind. That
640
634
  * check spans sections and therefore lives in the cloud, not here.
641
635
  */
@@ -816,12 +810,12 @@ export declare const robotConfigDoc: z.ZodObject<{
816
810
  }, z.core.$strict>;
817
811
  export type RobotConfigDoc = z.infer<typeof robotConfigDoc>;
818
812
  /**
819
- * One thing the cloud has to say about a configuration (spec §11.5: field +
820
- * violated rule).
813
+ * One thing the cloud has to say about a configuration: the field, and the
814
+ * rule it violates.
821
815
  *
822
816
  * `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).
817
+ * warning on purpose, because configuring a robot that has never been connected
818
+ * must stay possible.
825
819
  */
826
820
  export declare const validationIssue: z.ZodObject<{
827
821
  path: z.ZodString;
@@ -836,8 +830,7 @@ export declare const validationIssue: z.ZodObject<{
836
830
  export type ValidationIssue = z.infer<typeof validationIssue>;
837
831
  /**
838
832
  * 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).
833
+ * "draft newer than published", "published v2 · applied v1 · bridge offline".
841
834
  */
842
835
  export declare const configState: z.ZodObject<{
843
836
  published_version: z.ZodNullable<z.ZodNumber>;