@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/rest.d.ts CHANGED
@@ -1,7 +1,8 @@
1
+ // SPDX-License-Identifier: Apache-2.0
1
2
  import { z } from 'zod';
2
3
  /**
3
- * REST shapes of the robot resource (spec §11.1). W1 scope: create, list,
4
- * get, and the built-in `bridge_state` datapoint read.
4
+ * REST shapes of the robot resource: create, list, get, and the built-in
5
+ * `bridge_state` datapoint read.
5
6
  */
6
7
  export declare const robot: z.ZodObject<{
7
8
  id: z.ZodUUID;
@@ -23,8 +24,8 @@ export declare const createRobotRequest: z.ZodObject<{
23
24
  }, z.core.$strip>;
24
25
  export type CreateRobotRequest = z.infer<typeof createRobotRequest>;
25
26
  /**
26
- * The robot token binds one bridge to one robot (spec §5). It is returned
27
- * exactly once, here; the cloud stores only a hash of it.
27
+ * The robot token binds one bridge to one robot. It is returned exactly once,
28
+ * here; the cloud stores only a hash of it.
28
29
  */
29
30
  export declare const robotToken: z.ZodString;
30
31
  export declare const createRobotResponse: z.ZodObject<{
@@ -37,10 +38,10 @@ export declare const createRobotResponse: z.ZodObject<{
37
38
  }, z.core.$strip>;
38
39
  export type CreateRobotResponse = z.infer<typeof createRobotResponse>;
39
40
  /**
40
- * How many things a robot exposes, per kind (spec `2026-08-21-exposure-and-revoke-design` D1).
41
+ * How many things a robot exposes, per kind.
41
42
  *
42
- * **Five numbers, never a sum.** `robotDeletionSummary.slug_count` already made
43
- * this call and wrote down why: fold cameras in and the sentence "this deletes
43
+ * **Five numbers, never a sum.** `robotDeletionSummary.slug_count` makes the
44
+ * same call for the same reason: fold cameras in and the sentence "this deletes
44
45
  * N slugs and M cameras" counts them twice. A list row has the same problem.
45
46
  *
46
47
  * **Counted from the published configuration, and excluding the built-ins.**
@@ -105,7 +106,7 @@ export declare const robotListResponse: z.ZodObject<{
105
106
  export type RobotListResponse = z.infer<typeof robotListResponse>;
106
107
  /**
107
108
  * The REST read of one datapoint. For bridge-captured data `timestamp_ms`
108
- * is the capture time at the bridge (spec §6.3); for the cloud-observed
109
+ * is the capture time at the bridge; for the cloud-observed
109
110
  * built-in `bridge_state` it is the time the cloud observed the state.
110
111
  */
111
112
  export declare const datapointValue: z.ZodObject<{
@@ -117,7 +118,7 @@ export type DatapointValue = z.infer<typeof datapointValue>;
117
118
  /**
118
119
  * One robot in full: what the list shows, plus what only the detail view
119
120
  * needs — which bridge build is connected, why the last hello was refused,
120
- * and where the configuration stands (spec §15.2, tab 1).
121
+ * and where the configuration stands.
121
122
  */
122
123
  export declare const robotDetailResponse: z.ZodObject<{
123
124
  bridge_version: z.ZodNullable<z.ZodString>;
@@ -391,19 +392,16 @@ export type ConfigDraftResponse = z.infer<typeof configDraftResponse>;
391
392
  * the server parses it, and there is exactly one account of what the
392
393
  * configuration says.
393
394
  *
394
- * It also settles who owns parsing, and **FL-005 D2 moved that line**. The
395
- * sentence here used to read that the console refuses unparsable YAML before it
396
- * sends, so a syntax error never reaches the server. That is no longer the
397
- * rule: the **server** refuses text that is not valid YAML, with the line and
398
- * column, and stores everything else — including valid YAML that is not a
399
- * fleetless document, which comes back with `doc: null` and its issues. The
400
- * console checks as you type so the answer is immediate; the server checks
401
- * because it is the one that decides. Two checks of one question, and the
402
- * server's is the one that binds.
403
- *
404
- * The pair that used to be called a defect — a stored source that does not
405
- * parse to its stored document — is now a **represented state**: no document at
406
- * all. See `configDraftResponse` above.
395
+ * It also settles who owns parsing. The **server** refuses text that is not
396
+ * valid YAML, with the line and column, and stores everything else — including
397
+ * valid YAML that is not a fleetless document, which comes back with
398
+ * `doc: null` and its issues. An editor may check as you type so the answer is
399
+ * immediate; the server checks because it is the one that decides. Two checks
400
+ * of one question, and the server's is the one that binds.
401
+ *
402
+ * A stored source that does not parse to a document is therefore a
403
+ * **represented state**, not an error: no document at all. See
404
+ * `configDraftResponse` above.
407
405
  */
408
406
  export declare const putConfigDraftRequest: z.ZodObject<{
409
407
  source: z.ZodString;
@@ -683,11 +681,11 @@ export type FetchTypesResponse = z.infer<typeof fetchTypesResponse>;
683
681
  /**
684
682
  * What a client can read on this robot: the built-ins plus everything the
685
683
  * published configuration exposes. This is the seed of the generated
686
- * per-robot API (§11.2).
684
+ * per-robot API.
687
685
  *
688
- * **The OpenAPI rendering exists since the route manifest (`routes.ts`):
689
- * `artifacts/openapi.json`, derived from the manifest and these schemas by
690
- * `scripts/export-schemas.ts`.**
686
+ * **The OpenAPI rendering is `artifacts/openapi.json`**, derived from the
687
+ * route manifest in `routes.ts` and these schemas by
688
+ * `scripts/export-schemas.ts`.
691
689
  */
692
690
  export declare const datapointDescriptor: z.ZodObject<{
693
691
  slug: z.ZodString;
@@ -706,8 +704,8 @@ export declare const datapointListResponse: z.ZodObject<{
706
704
  }, z.core.$strip>;
707
705
  export type DatapointListResponse = z.infer<typeof datapointListResponse>;
708
706
  /**
709
- * The built-in `robot_details` datapoint (spec §4.3): static properties the
710
- * developer maintains. Bounded so one robot cannot become a document store.
707
+ * The built-in `robot_details` datapoint: static properties the developer
708
+ * maintains. Bounded so one robot cannot become a document store.
711
709
  */
712
710
  export declare const robotDetailsDoc: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnknown>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
713
711
  export type RobotDetailsDoc = z.infer<typeof robotDetailsDoc>;
@@ -721,7 +719,7 @@ export declare const putRobotDetailsRequest: z.ZodObject<{
721
719
  }, z.core.$strip>;
722
720
  export type PutRobotDetailsRequest = z.infer<typeof putRobotDetailsRequest>;
723
721
  /**
724
- * Invoke an action or call a service; parameters by field path (§4.4).
722
+ * Invoke an action or call a service; parameters by field path.
725
723
  *
726
724
  * Flat, keyed by `parameterSpec.name` — see `cloudInvoke.params` for why the
727
725
  * flat form is the one that makes a refusal legible.
@@ -732,55 +730,45 @@ export declare const invokeRequest: z.ZodObject<{
732
730
  }, z.core.$strip>;
733
731
  export type InvokeRequest = z.infer<typeof invokeRequest>;
734
732
  /**
735
- * The answer to an invoke. The job id is informative (§11.3): state is
736
- * observed by slug afterwards, over polling or a subscription.
733
+ * The answer to an invoke. The job id is informative: state is observed by
734
+ * slug afterwards, over polling or a subscription.
737
735
  */
738
736
  /**
739
- * The body of a cancel (W6b). **Every field optional, and the body itself may
740
- * be absent** — `POST .../cancel` was bodyless before this wave and every
741
- * existing caller still sends nothing.
742
- *
743
- * That is not politeness, it is the W5 defect: a bodyless `POST` carrying
744
- * `content-type: application/json` was rejected outright, which made
745
- * `cameras.live()` unreachable through the SDK and took `cancel`, publish,
746
- * restore, key rotation and member removal with it — unnoticed since W4. A
747
- * schema that demands a body would reintroduce it on the one verb that stops
748
- * a machine.
737
+ * The body of a cancel. **Every field optional, and the body itself may be
738
+ * absent** — `POST .../cancel` takes no body at all in its simplest form, and
739
+ * a schema that demanded one would break every caller on the one verb that
740
+ * stops a machine.
749
741
  *
750
742
  * **`.strict()`, and that is the whole point of the shape.** A plain object
751
743
  * strips unknown keys, so a caller who *means* to name a job and misspells the
752
744
  * field — `jobId` for `job_id` — has their id silently removed and gets the
753
745
  * **slug-wide** cancel instead: the most destructive reading of a request they
754
- * did not make. Measured in W6b's review: `{"jobId": "<some other job>"}`
755
- * answered `200` and stopped the job that was actually running, which nobody
756
- * had named. The `?force=true` precedent this route's design borrowed from
757
- * fails *safe* on a typo — a misspelled `force` simply does not force.
758
- * Stripping here fails unsafe, so unknown keys are refused instead.
746
+ * did not make. A `?force=true` flag fails *safe* on a typo, because a
747
+ * misspelled `force` simply does not force. Stripping here fails unsafe, so
748
+ * unknown keys are refused instead.
759
749
  *
760
750
  * `job_id` absent and `job_id: null` mean the **same** thing here, and that is
761
- * deliberate: over REST an absent body is how every caller written before this
762
- * wave says "cancel whatever is running". On the socket, `clientCancel.job_id`
763
- * is required-and-nullable instead, because a frame is assembled fresh by a
764
- * client that has already been updated — there, `null` is a decision and an
765
- * omission is a bug.
751
+ * deliberate: over REST an absent body is how a caller says "cancel whatever is
752
+ * running". On the socket, `clientCancel.job_id` is required-and-nullable
753
+ * instead, because a frame is assembled fresh by a client that knows this
754
+ * contract — there, `null` is a decision and an omission is a bug.
766
755
  */
767
756
  export declare const cancelRequest: z.ZodObject<{
768
757
  job_id: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
769
758
  }, z.core.$strict>;
770
759
  export type CancelRequest = z.infer<typeof cancelRequest>;
771
760
  /**
772
- * The query of a live release (W6b): `DELETE .../live?session_id=<uuid>`.
761
+ * The query of a live release: `DELETE .../live?session_id=<uuid>`.
773
762
  *
774
763
  * A query parameter rather than a body, following `?force=true` on robot
775
- * deletion — the precedent this repo already set for "a DELETE that needs one
776
- * more fact". A body on a DELETE is carried inconsistently by proxies and by
764
+ * deletion. A body on a DELETE is carried inconsistently by proxies and by
777
765
  * `fetch` itself, and this call runs from a browser tab that is often closing.
778
766
  *
779
767
  * **`.strict()`, for the reason `cancelRequest` is** — `?sessionid=` instead of
780
- * `?session_id=` was measured releasing **both** of an identity's holds and
781
- * stranding the other tab, which is precisely the defect this field was added
782
- * to remove. A refused typo costs a round trip; a stripped one stops a robot
783
- * somebody else is watching.
768
+ * `?session_id=` would release **every** one of an identity's holds and strand
769
+ * its other tabs, which is precisely what this field exists to prevent. A
770
+ * refused typo costs a round trip; a stripped one stops a robot somebody else
771
+ * is watching.
784
772
  *
785
773
  * Absent means today's meaning: release **all** of this identity's holds on
786
774
  * this camera. A client that has lost its id, or is going away entirely, still
@@ -882,14 +870,12 @@ export type PublishRequest = z.infer<typeof publishRequest>;
882
870
  * The **most recent** job on a slug — running or already finished — or null
883
871
  * only when nothing has ever run there.
884
872
  *
885
- * It said "the job currently running" until W4's review, and that quietly
886
- * made §11.3's first sentence false. The spec offers two equal ways to
887
- * observe a slug — *"Polling (REST) oder Subscription (Realtime)"* — but a
888
- * route that forgets a job the moment it settles lets a poller see only
889
- * `running`, then `null`. Succeeded, failed, cancelled, `lost` and
890
- * never-invoked all become the same answer, so §6.1's promise that a lost
891
- * job is *said out loud* held for subscribers and silently did not hold for
892
- * anyone polling. It is also the recovery `command_outcome_unknown` points
873
+ * A slug can be observed two equally valid ways, by polling this route or by
874
+ * subscribing. A route that forgot a job the moment it settled would let a
875
+ * poller see only `running`, then `null`: succeeded, failed, cancelled, `lost`
876
+ * and never-invoked would all become the same answer, and the promise that a
877
+ * lost job is said out loud would hold for subscribers and silently not hold
878
+ * for anyone polling. It is also the recovery `command_outcome_unknown` points
893
879
  * a caller to.
894
880
  *
895
881
  * Read `job.state` to tell a live job from a finished one; that is what the
@@ -921,20 +907,18 @@ export declare const jobResponse: z.ZodObject<{
921
907
  export type JobResponse = z.infer<typeof jobResponse>;
922
908
  /**
923
909
  * Every job the platform currently believes this robot has — `GET
924
- * /api/robots/:id/jobs` (W6b).
910
+ * /api/robots/:id/jobs`.
925
911
  *
926
912
  * `jobResponse` answers "what is on this slug", which requires knowing the
927
- * slug first. That was enough while a job could only exist on a slug the
928
- * published configuration named. W6b breaks that assumption twice: a
929
- * reconnecting bridge can name a job the cloud has **no row for** and the
930
- * cloud adopts it, and a configuration change can leave a job on a slug the
931
- * document no longer contains. Both are jobs nobody can ask about, because
932
- * asking requires already knowing what to ask for.
933
- *
934
- * So this route exists to answer the question the per-slug route cannot: not
935
- * "is something running here", but "what is this robot doing". A restarted
936
- * cloud that has just reconciled a robot's `hello.active_jobs` has exactly
937
- * this list and, until now, no way to say it out loud.
913
+ * slug first. Two kinds of job break that assumption: a reconnecting bridge
914
+ * can name a job the cloud has **no row for**, and the cloud adopts it; and a
915
+ * configuration change can leave a job on a slug the document no longer
916
+ * contains. Both are jobs nobody can ask about, because asking requires
917
+ * already knowing what to ask for.
918
+ *
919
+ * So this route answers the question the per-slug route cannot: not "is
920
+ * something running here", but "what is this robot doing". A cloud that has
921
+ * just reconciled a robot's `hello.active_jobs` has exactly this list.
938
922
  *
939
923
  * The array is ordered newest first and is **never null**: a robot doing
940
924
  * nothing answers `{ jobs: [] }`. "Nothing is running" and "we did not look"
@@ -942,14 +926,11 @@ export type JobResponse = z.infer<typeof jobResponse>;
942
926
  * distinction `robotDeletionSummary` was made all-required for.
943
927
  *
944
928
  * **At most one entry per slug: the current job there, exactly what
945
- * `jobResponse` would answer for that slug.** This is not a history endpoint
946
- * and must not become one. The first implementation returned every job the
947
- * registry still held — six rows and four complete Fibonacci results after a
948
- * few minutes of gate traffic, and unbounded in both count and payload for a
949
- * robot that has been working all day. The list would have grown until a
950
- * console page carried a robot's entire past, and the one thing it exists to
951
- * answer — *what is this robot doing* — would have been the first line of a
952
- * scroll.
929
+ * `jobResponse` would answer for that slug.** This is not a history endpoint.
930
+ * Returning every job a registry still holds is unbounded in both count and
931
+ * payload for a robot that has been working all day, and the one thing this
932
+ * route exists to answer — *what is this robot doing* — would be the first
933
+ * line of a scroll. The durable history has its own routes.
953
934
  *
954
935
  * A settled job stays visible as its slug's current entry until something
955
936
  * else runs there, which is what makes a job that just failed still findable.
@@ -957,7 +938,7 @@ export type JobResponse = z.infer<typeof jobResponse>;
957
938
  * `jobResponse`.
958
939
  */
959
940
  /**
960
- * What a `rate_limited` refusal tells the caller (W6c).
941
+ * What a `rate_limited` refusal tells the caller.
961
942
  *
962
943
  * One number, and it is the only one that matters: **when to come back.** A
963
944
  * limit that says "too many" without saying "in 800 ms" produces a client that
@@ -974,11 +955,10 @@ export declare const rateLimitDetails: z.ZodObject<{
974
955
  export type RateLimitDetails = z.infer<typeof rateLimitDetails>;
975
956
  /**
976
957
  * Every job this robot's registry currently holds, **ordered newest first by
977
- * `started_at`, with `seq` as the tiebreaker** (W7, register rows 2j and 2l).
958
+ * `started_at`, with `seq` as the tiebreaker**.
978
959
  *
979
- * The field is named because the previous version of this comment claimed an
980
- * order without saying what produced it, and the answer turned out to matter
981
- * twice over:
960
+ * The tiebreaker is named rather than left implicit, because it matters twice
961
+ * over:
982
962
  *
983
963
  * 1. **`started_at` alone is not a total order.** Two jobs minted in the same
984
964
  * millisecond sorted against each other arbitrarily — differently on each
@@ -1021,9 +1001,9 @@ export type RobotJobsResponse = z.infer<typeof robotJobsResponse>;
1021
1001
  /**
1022
1002
  * Every slug of a robot that a role can be granted, **with its kind**.
1023
1003
  *
1024
- * The roles matrix was built in W3 against the datapoint list, which was the
1025
- * only kind that existed. With four kinds it needs one list that names them,
1026
- * or the matrix silently cannot grant an action.
1004
+ * A roles matrix built against the datapoint list alone cannot grant an
1005
+ * action, a service or a publisher. One list that names every kind, with its
1006
+ * kind, is what a matrix needs.
1027
1007
  */
1028
1008
  export declare const exposure: z.ZodObject<{
1029
1009
  slug: z.ZodString;
@@ -1072,36 +1052,27 @@ export declare const SNAPSHOT_HEADERS: {
1072
1052
  readonly height: "x-fleetless-height";
1073
1053
  };
1074
1054
  /**
1075
- * The metadata an asset upload carries beside its raw body (W7).
1055
+ * The metadata an asset upload carries beside its raw body.
1076
1056
  *
1077
- * Here rather than as a convention documented on both sides, and the reason is
1078
- * a scar. W5 shipped `x-fleetless-*` headers the CORS policy did not expose,
1079
- * so `age_ms` was `null` in **every** browser while the SDK documented `null`
1080
- * as "nothing captured yet" — a fresh frame reporting as no snapshot at all,
1081
- * invisible to three test suites because none of them was a browser. And W6b
1082
- * found the general form: three repos agreeing with each other about a payload
1083
- * none of them exchanged, each right in its own tests.
1084
- *
1085
- * **A string shared by two repos and defined in both is a string that drifts.**
1086
- * A zod schema cannot validate a header, which is an argument for writing the
1087
- * names down once, not an argument for writing them down twice.
1057
+ * Written here rather than left as a convention each side documents for
1058
+ * itself. **A string shared by two implementations and defined in both is a
1059
+ * string that drifts**, and a header is the easiest place for that to happen
1060
+ * unnoticed: a zod schema cannot validate one, which is an argument for
1061
+ * writing the names down once, not an argument for writing them down twice.
1088
1062
  *
1089
1063
  * `name` is the `package://` URI verbatim for a mesh — the same string
1090
1064
  * `asset.name` stores, and the same one `urdfCompleteness.missing` reports, so
1091
1065
  * a failed upload and a missing mesh can be matched by eye.
1092
1066
  */
1093
1067
  /**
1094
- * **`name` travels percent-encoded, and that is a fix rather than a
1095
- * convention** (W7a review, André's decision to fix rather than defer).
1096
- *
1097
- * HTTP header values are latin-1 (`http.client` in Python, and the same is
1098
- * true on the other side). So a texture called `textures/日本語.png` raised a
1099
- * `UnicodeEncodeError` **inside `urllib`** — a `ValueError`, caught by neither
1100
- * `HTTPError` nor `URLError` — which propagated to the sync's broad handler
1101
- * and marked **everything still remaining** as failed. One non-ASCII filename
1102
- * cost a developer every mesh after it in that sync, with no cause on the
1103
- * wire. R6 made it ordinary rather than exotic: `.dae` internal names come
1104
- * from 3D-authoring tools, where non-ASCII is Tuesday.
1068
+ * **`name` travels percent-encoded in a second header.**
1069
+ *
1070
+ * HTTP header values are latin-1. A texture called `textures/日本語.png` cannot
1071
+ * be put in one at all: in Python it raises a `UnicodeEncodeError` inside
1072
+ * `urllib` — a `ValueError`, caught by neither `HTTPError` nor `URLError` — so
1073
+ * a single non-ASCII filename can fail an entire sync with no cause on the
1074
+ * wire. Non-ASCII names are ordinary rather than exotic, because `.dae`
1075
+ * internal names come from 3D-authoring tools.
1105
1076
  *
1106
1077
  * The encoding is not invented here. **`GET .../assets/missing?name=` already
1107
1078
  * carries this exact string percent-encoded**, because a query parameter is
@@ -1109,21 +1080,19 @@ export declare const SNAPSHOT_HEADERS: {
1109
1080
  * answered.
1110
1081
  *
1111
1082
  * **It is a SECOND header, and that is the whole design rather than a
1112
- * detail.** The first version overloaded `name` itself: the producer would
1113
- * encode, the store would `decodeURIComponent`. That decodes identically for
1114
- * every name without a `%`, so an **older bridge and a newer cloud agree by
1115
- * luck** — right up until a name contains `%2f`, which the store would then
1116
- * silently turn into a `/`. A wire change whose breakage is invisible in the
1117
- * common case and silent in the uncommon one is the worst of both (Argus-W7a,
1118
- * reading the contract rather than the code).
1083
+ * detail.** Overloading `name` itself — the producer encodes, the store
1084
+ * decodes — decodes identically for every name without a `%`, so an older
1085
+ * producer and a newer store agree by luck right up until a name contains
1086
+ * `%2f`, which the store would then silently turn into a `/`. A wire change
1087
+ * whose breakage is invisible in the common case and silent in the uncommon
1088
+ * one is the worst of both.
1119
1089
  *
1120
1090
  * So `name` keeps meaning exactly what it always meant, and `nameEncoded`
1121
1091
  * carries the percent-encoded UTF-8 form. **The store prefers `nameEncoded`
1122
1092
  * when present and uses `name` otherwise**, so:
1123
1093
  *
1124
- * - an older bridge sends only `name` and behaves exactly as before;
1125
- * - a newer bridge sends both, and a name it cannot express in latin-1 travels
1126
- * intact for the first time;
1094
+ * - a producer that sends only `name` behaves exactly as it always did;
1095
+ * - a producer that sends both can carry a name latin-1 cannot express;
1127
1096
  * - no value is ever ambiguous about which encoding it is in.
1128
1097
  *
1129
1098
  * A producer that can send `nameEncoded` should send both, so a store older
@@ -1136,23 +1105,22 @@ export declare const ASSET_UPLOAD_HEADERS: {
1136
1105
  readonly nameEncoded: "x-fleetless-asset-name-encoded";
1137
1106
  readonly syncId: "x-fleetless-sync-id";
1138
1107
  /**
1139
- * **Die angekündigte Größe, und sie ist der Grund, warum `asset_too_large`
1140
- * überhaupt entstehen kann (W9b, DEF-116).**
1108
+ * **The announced size, and it is what makes `asset_too_large` reachable at
1109
+ * all.**
1141
1110
  *
1142
- * Fastifys `bodyLimit` greift im Content-Type-Parser, also **vor** dem
1143
- * Handler — eine zu große Datei bekam damit ein blankes `413 bad_request`
1144
- * ohne `limit_bytes` und ohne `size_bytes`, und der strukturierte Fehlercode,
1145
- * den `assetTooLargeDetails` beschreibt, hatte schlicht keinen erreichbaren
1146
- * Erzeuger (Momus-W7, M1, an den echten Routenoptionen reproduziert).
1111
+ * A server-side body limit is applied by the content-type parser, before the
1112
+ * handler runs, so an oversized upload can only be refused with a bare
1113
+ * `413` carrying neither `limit_bytes` nor `size_bytes` — and the structured
1114
+ * refusal `assetTooLargeDetails` describes would have no producer.
1147
1115
  *
1148
- * Mit einer angekündigten Größe im Kopf kann die Ablehnung dort entstehen,
1149
- * wo sie etwas sagen kann: bevor ein Byte gepuffert ist, mit beiden Zahlen.
1150
- * Und die Bridge erfährt ihre Grenze, ohne 194 MB zu lesen, um sie zu
1151
- * entdecken — was am 2026-08-18 auf rx1 genau so ausging (DEF-148).
1116
+ * With the size announced in a header the refusal can be made where it can
1117
+ * say something: before a byte is buffered, with both numbers. It also lets
1118
+ * a producer discover its own limit without first reading the whole file
1119
+ * into memory.
1152
1120
  *
1153
- * Der Kopf ist eine **Ankündigung, kein Beweis**: Ein Absender kann lügen.
1154
- * Der Deckel gilt weiterhin auch am Körper — dies ersetzt die Durchsetzung
1155
- * nicht, es macht die Absage nur beantwortbar.
1121
+ * The header is an **announcement, not a proof**: a sender can lie. The
1122
+ * ceiling still applies to the body — this does not replace enforcement, it
1123
+ * only makes the refusal answerable.
1156
1124
  */
1157
1125
  readonly size: "x-fleetless-asset-size";
1158
1126
  };
@@ -1178,7 +1146,7 @@ export type CameraListResponse = z.infer<typeof cameraListResponse>;
1178
1146
  * What a viewer needs to join, and **what it costs them to hold**.
1179
1147
  *
1180
1148
  * `POST` takes a refcount hold and `DELETE` releases it; the first hold
1181
- * starts the robot publishing and the last release stops it (§10). A client
1149
+ * starts the robot publishing and the last release stops it. A client
1182
1150
  * that forgets to release keeps a robot streaming to nobody, so the SDK hands
1183
1151
  * back a `release()` rather than a bare token.
1184
1152
  *
@@ -1210,8 +1178,8 @@ export type LiveSessionResponse = z.infer<typeof liveSessionResponse>;
1210
1178
  * which polls continuously while a tab is open.
1211
1179
  *
1212
1180
  * `age_ms` is not a convenience: a cached frame served without its age is
1213
- * indistinguishable from a live one, and §10 makes snapshots deliberately
1214
- * cheap and therefore deliberately old. `null` values mean nothing has been
1181
+ * indistinguishable from a live one, and snapshots are deliberately cheap and
1182
+ * therefore deliberately old. `null` values mean nothing has been
1215
1183
  * captured yet — which is an answer, not an error.
1216
1184
  */
1217
1185
  export declare const snapshotMetaResponse: z.ZodObject<{
@@ -1224,28 +1192,23 @@ export declare const snapshotMetaResponse: z.ZodObject<{
1224
1192
  }, z.core.$strip>;
1225
1193
  export type SnapshotMetaResponse = z.infer<typeof snapshotMetaResponse>;
1226
1194
  /**
1227
- * **Both history shapes answer the same boundary the same way: `[from, to)`
1228
- * (W9d, DEF-062 — decision pre-made at the W6 boundary so no wave
1229
- * re-litigates it).**
1230
- *
1231
- * They did not. `samples` was inclusive of `to`, `buckets` exclusive — same
1232
- * range, same data, opposite answers for a point landing exactly on `to`, and
1233
- * the buckets answer rendered as a gap tooltipped *"empty — no samples"*.
1234
- * `sdk/README.md` documented the inclusive notation for the half-open path,
1235
- * so it was wrong for one of the two whichever way you read it.
1236
- *
1237
- * Half-open wins because it is the only rule under which **adjacent windows
1238
- * tile without overlap**: `[0,10)` then `[10,20)` covers every instant once.
1239
- * With an inclusive upper bound a sample at exactly `10` belongs to both
1240
- * windows, and any consumer summing them counts it twice.
1241
- *
1242
- * This is a statement about behaviour, not a field — nothing in the shapes
1243
- * below can enforce it. It is written here because this is the one place both
1244
- * shapes are defined together, and the cloud's `history-store` and the SDK's
1245
- * README are the two places that have to agree with it.
1195
+ * **Both history shapes answer the same boundary the same way: `[from, to)`.**
1196
+ *
1197
+ * Half-open, because it is the only rule under which **adjacent windows tile
1198
+ * without overlap**: `[0,10)` then `[10,20)` covers every instant once. With an
1199
+ * inclusive upper bound a sample at exactly `10` belongs to both windows, and
1200
+ * any consumer summing them counts it twice.
1201
+ *
1202
+ * Two shapes that answered it differently would give opposite results for a
1203
+ * point landing exactly on `to` — same range, same data — and the difference
1204
+ * renders as a gap in one of the two.
1205
+ *
1206
+ * This is a statement about behaviour, not a field: nothing in the shapes below
1207
+ * can enforce it. It is written here because this is the one place both shapes
1208
+ * are defined together.
1246
1209
  */
1247
1210
  /**
1248
- * A history query (§8). `from`/`to` accept **either** a relative expression
1211
+ * A history query. `from`/`to` accept **either** a relative expression
1249
1212
  * (`now-30s`, `now-5m`, `now-1h`) **or** absolute unix milliseconds, because
1250
1213
  * a chart asks the first way and a report asks the second, and making a
1251
1214
  * client convert is making it guess our clock.
@@ -1268,7 +1231,7 @@ export declare const historyQuery: z.ZodObject<{
1268
1231
  }, z.core.$strip>;
1269
1232
  export type HistoryQuery = z.infer<typeof historyQuery>;
1270
1233
  /**
1271
- * Raw samples. `timestamp_ms` is the **bridge's capture time** (§6.3) — the
1234
+ * Raw samples. `timestamp_ms` is the **bridge's capture time** — the
1272
1235
  * same instant the live value carried, so a recorded point and a live one can
1273
1236
  * be placed on one axis without apology.
1274
1237
  *
@@ -1296,9 +1259,8 @@ export type HistorySamplesResponse = z.infer<typeof historySamplesResponse>;
1296
1259
  * inspection.
1297
1260
  *
1298
1261
  * `sample_count` exists because an empty bucket and a bucket whose average is
1299
- * zero are different facts. W5 established at some cost what happens when two
1300
- * facts share one representation, and a chart is the easiest place in this
1301
- * product to draw a gap as a line.
1262
+ * zero are different facts. When two facts share one representation, a chart
1263
+ * is the easiest place to draw a gap as a line.
1302
1264
  */
1303
1265
  export declare const historyBucketsResponse: z.ZodObject<{
1304
1266
  slug: z.ZodString;
@@ -1365,7 +1327,7 @@ export declare const historyResponse: z.ZodUnion<readonly [z.ZodObject<{
1365
1327
  }, z.core.$strip>]>;
1366
1328
  export type HistoryResponse = z.infer<typeof historyResponse>;
1367
1329
  /**
1368
- * W6a — deletion, and the one channel that reports health.
1330
+ * Deletion, and the one channel that reports health.
1369
1331
  *
1370
1332
  * | Route | Body | Answer |
1371
1333
  * |---|---|---|
@@ -1391,24 +1353,17 @@ export type HistoryResponse = z.infer<typeof historyResponse>;
1391
1353
  * takes an optional `robot_id` filter rather than living at a per-robot
1392
1354
  * path.
1393
1355
  *
1394
- * The first version of this table said the opposite, with a justification
1395
- * that sounded right and was incomplete: it reasoned only from a page that
1396
- * has just opened one robot. But the console shows health on the **robot
1397
- * list** too, and a per-robot path makes that N requests to render one
1398
- * screen — while the event that must keep it fresh arrives org-wide anyway.
1399
- * A snapshot and a channel that disagree about scope are not two halves of
1400
- * one thing; they are two things that have to be reconciled by every
1401
- * consumer, separately, forever.
1402
- *
1403
- * So: same scope, one route, and `?robot_id=` for the narrow question. The
1404
- * cloud owner proposed this while unblocking the console, and was right.
1405
- *
1406
- * This table was missing from the first W6a delta, and a teammate had to ask
1407
- * three separate people for the paths — which is how a route becomes a fact
1408
- * that lives only in an inbox.
1356
+ * The per-robot reading is the tempting one and it is wrong: a health list is
1357
+ * rendered for every robot at once, and a per-robot path makes that N requests
1358
+ * to draw one screen — while the event that keeps it fresh arrives org-wide
1359
+ * anyway. A snapshot and a channel that disagree about scope are not two halves
1360
+ * of one thing; they are two things every consumer has to reconcile, separately,
1361
+ * forever.
1362
+ *
1363
+ * So: same scope, one route, and `?robot_id=` for the narrow question.
1409
1364
  */
1410
1365
  /**
1411
- * What a `robot.deleted` audit event carries (W6a).
1366
+ * What a `robot.deleted` audit event carries.
1412
1367
  *
1413
1368
  * A deletion record that says only *that* something was destroyed is a
1414
1369
  * receipt for an unknown amount. This names it: how many configured slugs,
@@ -1452,41 +1407,36 @@ export declare const robotDeleteQuery: z.ZodObject<{
1452
1407
  }, z.core.$strip>;
1453
1408
  export type RobotDeleteQuery = z.infer<typeof robotDeleteQuery>;
1454
1409
  /**
1455
- * The seven health states, declared **once** (W6a review).
1410
+ * The seven health states, declared **once**.
1456
1411
  *
1457
- * `resourceHealthState` and `resourceHealthEvent` are the snapshot and the
1458
- * push of the same thing, and they had the same seven values written out
1459
- * twice, linked by nothing — the artifacts published two independent copies
1460
- * with no `$ref`. They agreed only because whoever added `unknown` remembered
1461
- * to add it in both places, on the wave's last contract commit.
1412
+ * `resourceHealthState` and `resourceHealthEvent` are the snapshot and the push
1413
+ * of the same thing. Writing the values out twice publishes two independent
1414
+ * artifacts with no `$ref` between them, kept in step only by whoever
1415
+ * remembers to edit both.
1462
1416
  *
1463
- * One concept rendering as two artifacts that nothing keeps in step is its
1464
- * own class of artifact-versus-source defect, distinct from `.default()`
1465
- * publishing as `required` and from `z.coerce`'s unrepresentable input.
1417
+ * One concept rendering as two artifacts that nothing keeps in step is its own
1418
+ * class of artifact-versus-source defect, distinct from `.default()` publishing
1419
+ * as `required` and from a coercion's unrepresentable input.
1466
1420
  */
1467
1421
  export declare const RESOURCE_HEALTH_STATES: readonly ["ok", "unreachable", "auth_failed", "unreadable_credential", "credential_missing", "stopped_by_config_change", "publish_failed", "unknown"];
1468
1422
  /**
1469
1423
  * The health of one thing a developer configured, as the platform currently
1470
- * sees it (W6a).
1471
- *
1472
- * This exists because four separate findings turned out to be one absence:
1473
- * nothing carried the state of a camera, a source or a credential to a
1474
- * developer who was not, at that exact moment, pressing a button. A publish
1475
- * failure after the `201` never reached the viewer holding the token; a
1476
- * source whose password was wrong failed at config-apply time with nobody
1477
- * watching and stayed silent until someone pressed "Go live" days later; a
1478
- * viewer could not learn *why* a stream ended, so the console had to offer
1479
- * two possibilities and rank neither; and an undecryptable credential
1480
- * reported as healthy.
1481
- *
1482
- * One shape, because four patches against four symptoms is how W5 nearly
1483
- * wrote a failure report into `publishState` — a field the cloud writes and
1484
- * reads in exactly one place, which would have been a dead end.
1485
- *
1486
- * `reason` is for a human and is **never** built from an exception message:
1487
- * W6 found a camera password in a log through `log.exception`, and again in
1488
- * `LiveStartError`'s message, which travels to the cloud on this very path.
1489
- * Type names and fixed strings only.
1424
+ * sees it.
1425
+ *
1426
+ * It exists because nothing else carries the state of a camera, a source or a
1427
+ * credential to a developer who is not, at that exact moment, pressing a
1428
+ * button. A publish failure after the `201` reaches no one; a source whose
1429
+ * password is wrong fails at config-apply time with nobody watching and stays
1430
+ * silent until someone presses "Go live" days later; a viewer cannot learn
1431
+ * *why* a stream ended; an undecryptable credential reports as healthy.
1432
+ *
1433
+ * One shape rather than a field per symptom, because a failure written into a
1434
+ * state field the platform writes and reads in one place is a dead end.
1435
+ *
1436
+ * `reason` is for a human and is **never** built from an exception message: a
1437
+ * camera password reaches a log that way, and an exception message from a
1438
+ * failing stream travels to the cloud on this very path. Type names and fixed
1439
+ * strings only.
1490
1440
  */
1491
1441
  export declare const resourceHealthState: z.ZodObject<{
1492
1442
  robot_id: z.ZodUUID;
@@ -1515,14 +1465,8 @@ export type ResourceHealthState = z.infer<typeof resourceHealthState>;
1515
1465
  /**
1516
1466
  * The current state of everything in the **org**.
1517
1467
  *
1518
- * This doc said "on one robot" until the W6a review found it: the route moved
1519
- * to org scope in `2bb67c5` and the route table forty lines above spends a
1520
- * paragraph explaining why the per-robot reading was wrong — while the schema
1521
- * it describes still said the old thing. Cloud, console and SDK all implement
1522
- * org-wide correctly; contracts was the only place still saying otherwise,
1523
- * and it is the first place a fourth consumer reads.
1524
- *
1525
- * A channel with no snapshot cannot answer "what is the state now?" for a
1468
+ * Org-wide, not per robot — the route table above explains why. A channel
1469
+ * with no snapshot cannot answer "what is the state now?" for a
1526
1470
  * page that just loaded — it can only report the next change, which may be
1527
1471
  * hours away. Both halves or neither.
1528
1472
  */
@@ -1564,9 +1508,9 @@ export declare const orgHealthQuery: z.ZodObject<{
1564
1508
  }, z.core.$strip>;
1565
1509
  export type OrgHealthQuery = z.infer<typeof orgHealthQuery>;
1566
1510
  /**
1567
- * Org protection quotas (§12.4) — generous, server-side adjustable, visible
1568
- * in Settings. Protection against runaway use, not a business model; a later
1569
- * one docks onto the same dials.
1511
+ * Org protection quotas — generous, server-side adjustable, visible in
1512
+ * settings. Protection against runaway use, not a business model; a later one
1513
+ * docks onto the same dials.
1570
1514
  */
1571
1515
  export declare const orgQuotas: z.ZodObject<{
1572
1516
  max_robots: z.ZodNumber;
@@ -1586,8 +1530,7 @@ export type OrgQuotas = z.infer<typeof orgQuotas>;
1586
1530
  * `positive()` because a quota of zero would forbid everything, but a
1587
1531
  * **usage** of zero is the honest answer for every org on the day it signs
1588
1532
  * up. Reusing one schema for a limit and a measurement is the same mistake as
1589
- * letting an empty bucket and a zero average share a representation, which
1590
- * this wave spent a lot of care avoiding one layer up.
1533
+ * letting an empty bucket and a zero average share a representation.
1591
1534
  *
1592
1535
  * Every field is optional because a quota we do not measure must be
1593
1536
  * **absent**, never reported as `0` — "not measured" and "measured as zero"
@@ -1649,11 +1592,9 @@ export declare const BRIDGE_LATENCY_RETENTION_DAYS = 7;
1649
1592
  * | `GET /api/org/latency` | `orgLatencyQuery` | `orgLatencyResponse` — one series per robot, truncation named |
1650
1593
  * | `GET /api/robots/:id/jobs/history` | `jobRunQuery` | `jobRunListResponse` — the same read, robot-scoped, developers **and** clients |
1651
1594
  *
1652
- * **Written down here because the last time a delta shipped shapes without
1653
- * their paths, a teammate had to ask three separate people** — see
1654
- * `robotDeletionSummary`'s neighbouring table, which exists for exactly that
1655
- * reason. The shapes landed one wave before the routes did, so this table is
1656
- * the only place the two halves meet.
1595
+ * The paths are written down beside the shapes, as in
1596
+ * `robotDeletionSummary`'s neighbouring table: a shape whose route is not
1597
+ * named here is a fact that lives only in somebody's memory.
1657
1598
  *
1658
1599
  * Three things about them are worth stating rather than inferring:
1659
1600
  *
@@ -1773,7 +1714,7 @@ export type OrgLatencyResponse = z.infer<typeof orgLatencyResponse>;
1773
1714
  */
1774
1715
  export declare const USAGE_WINDOW_MAX_DAYS = 366;
1775
1716
  /**
1776
- * The five things the meter records (spec D1).
1717
+ * The five things the meter records.
1777
1718
  *
1778
1719
  * Storage is two metrics and not one summed byte count, for
1779
1720
  * `org_quotas.max_asset_storage_bytes`'s own reason applied to billing: a sync
@@ -1815,7 +1756,7 @@ export type UsageMetric = z.infer<typeof usageMetric>;
1815
1756
  export declare const usageDay: z.ZodString;
1816
1757
  /**
1817
1758
  * **The window is inclusive at both ends**, unlike every millisecond window in
1818
- * this file (`from_ms`/`to_ms`, half-open per DEF-062).
1759
+ * this file (`from_ms`/`to_ms`, which are half-open).
1819
1760
  *
1820
1761
  * That inconsistency is deliberate and is stated here rather than left to be
1821
1762
  * discovered: a calendar day is a unit, not an instant, and a person asking for
@@ -1852,7 +1793,7 @@ export type OrgUsageQuery = z.infer<typeof orgUsageQuery>;
1852
1793
  /**
1853
1794
  * One day's reading for one metric.
1854
1795
  *
1855
- * **`app_id` is `null` when the consumer is the org itself** (spec D2), and
1796
+ * **`app_id` is `null` when the consumer is the org itself**, and
1856
1797
  * what that `null` means for billing depends on the *metric*, not on
1857
1798
  * `app_id` alone. `api_calls` and `live_session_ms` are attributable to an
1858
1799
  * app: a `null` app_id on those two is the developer console's own traffic,
@@ -1881,9 +1822,8 @@ export type OrgUsageQuery = z.infer<typeof orgUsageQuery>;
1881
1822
  * not hold at all: everything counted since the last successful flush is
1882
1823
  * held in memory, deliberately uncapped, and a `kill -9` loses all of it.
1883
1824
  * The trade is intentional (dropping billing data to bound process memory is
1884
- * the worse half of it), but "at most one interval" describes a platform
1885
- * whose writes are landing, not a guarantee that survives an outage. This
1886
- * sentence used to say "never more", and it was false.
1825
+ * the worse half of it), but "at most one interval" describes a platform whose
1826
+ * writes are landing, not a guarantee that survives an outage.
1887
1827
  *
1888
1828
  * A row the database rejects **permanently** — most concretely one whose org
1889
1829
  * has been deleted since the count, since a usage row's `org_id` is `ON
@@ -1974,11 +1914,10 @@ export type RenameSlugResponse = z.infer<typeof renameSlugResponse>;
1974
1914
  * `GET /api/robots/:id/config/slug-usage/:slug` — what a rename would touch;
1975
1915
  * feeds the console's confirm dialog.
1976
1916
  *
1977
- * `alert_count` (spec `2026-08-28-alerts-and-datapoint-modal-design`, D5)
1978
- * joined the atomic rename transaction alongside grants and history: alerts
1979
- * are keyed by `(robot_id, slug)` too, and a rename that silently moved the
1980
- * alert row while the usage preview stayed silent about it would show a
1981
- * developer a smaller blast radius than the rename actually has.
1917
+ * `alert_count` is part of the atomic rename transaction alongside grants and
1918
+ * history: alerts are keyed by `(robot_id, slug)` too, and a rename that
1919
+ * silently moved the alert row while the usage preview stayed silent about it
1920
+ * would show a developer a smaller blast radius than the rename actually has.
1982
1921
  */
1983
1922
  export declare const slugUsageResponse: z.ZodObject<{
1984
1923
  grant_count: z.ZodNumber;