@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.
- package/CHANGELOG.md +68 -2
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +136 -0
- package/README.md +49 -13
- package/SECURITY.md +55 -0
- package/artifacts/openapi.json +1 -1
- package/artifacts/routes.json +1 -1
- package/dist/alerts.d.ts +19 -24
- package/dist/alerts.js +18 -24
- package/dist/app-users.d.ts +7 -6
- package/dist/app-users.js +6 -6
- package/dist/apps.d.ts +21 -25
- package/dist/apps.js +40 -51
- package/dist/assets.d.ts +70 -132
- package/dist/assets.js +130 -223
- package/dist/audit.d.ts +11 -11
- package/dist/audit.js +25 -51
- package/dist/client-auth.d.ts +4 -4
- package/dist/client-auth.js +3 -4
- package/dist/common.d.ts +27 -35
- package/dist/common.js +26 -35
- package/dist/config-issues.d.ts +4 -3
- package/dist/config-issues.js +7 -6
- package/dist/config.d.ts +31 -37
- package/dist/config.js +81 -110
- package/dist/errors.d.ts +4 -3
- package/dist/errors.js +61 -87
- package/dist/identity.d.ts +18 -21
- package/dist/identity.js +17 -21
- package/dist/index.d.ts +4 -4
- package/dist/index.js +12 -13
- package/dist/introspection.d.ts +7 -6
- package/dist/introspection.js +6 -6
- package/dist/jobs.d.ts +12 -12
- package/dist/jobs.js +20 -25
- package/dist/mcp.d.ts +11 -12
- package/dist/mcp.js +10 -12
- package/dist/oauth.d.ts +13 -18
- package/dist/oauth.js +13 -19
- package/dist/protocol.d.ts +51 -62
- package/dist/protocol.js +107 -139
- package/dist/realtime.d.ts +53 -68
- package/dist/realtime.js +77 -103
- package/dist/rest.d.ts +182 -243
- package/dist/rest.js +301 -395
- package/dist/routes.d.ts +4 -3
- package/dist/routes.js +3 -2
- package/package.json +12 -7
package/dist/realtime.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
/**
|
|
3
|
-
* Client realtime protocol
|
|
4
|
-
*
|
|
5
|
-
* stream; command parity arrives in W4.
|
|
4
|
+
* Client realtime protocol: WebSocket subscriptions on datapoints, the
|
|
5
|
+
* datapoint event stream, and full command parity with REST.
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
|
-
* The first frame a client sends after the socket opens
|
|
8
|
+
* The first frame a client sends after the socket opens.
|
|
9
9
|
*
|
|
10
10
|
* A browser cannot set an `Authorization` header on a WebSocket handshake,
|
|
11
11
|
* and a token in the query string would outlive the request in server,
|
|
@@ -52,7 +52,7 @@ export declare const authError: z.ZodObject<{
|
|
|
52
52
|
}, z.core.$strip>;
|
|
53
53
|
export type AuthError = z.infer<typeof authError>;
|
|
54
54
|
/**
|
|
55
|
-
* Command parity
|
|
55
|
+
* Command parity: everything REST can do — invoke an action,
|
|
56
56
|
* call a service, publish, cancel — also travels over this socket.
|
|
57
57
|
*
|
|
58
58
|
* **Every command carries a `request_id` and every reply echoes it.** A
|
|
@@ -103,11 +103,10 @@ export type ClientPublish = z.infer<typeof clientPublish>;
|
|
|
103
103
|
* a command has reached the bridge, deliberately, so that the next command —
|
|
104
104
|
* a stop, say — is never held up behind bookkeeping. Work that follows the
|
|
105
105
|
* send therefore runs unordered: a publish that *acquires* a slug writes an
|
|
106
|
-
* audit record before answering, while an immediately following publish by
|
|
107
|
-
*
|
|
108
|
-
* overtakes.
|
|
109
|
-
*
|
|
110
|
-
* identity and slug — and not of a race.
|
|
106
|
+
* audit record before answering, while an immediately following publish by the
|
|
107
|
+
* now-current holder has nothing to write and answers at once. Its reply
|
|
108
|
+
* overtakes. That reversal happens at most once per identity and slug, which
|
|
109
|
+
* is what distinguishes it from a race.
|
|
111
110
|
*
|
|
112
111
|
* Serialising the replies would mean putting that bookkeeping in front of
|
|
113
112
|
* every following command, including the stop. The ordering that matters is
|
|
@@ -164,7 +163,7 @@ export declare const errorFrame: z.ZodObject<{
|
|
|
164
163
|
}, z.core.$strip>;
|
|
165
164
|
export type ErrorFrame = z.infer<typeof errorFrame>;
|
|
166
165
|
/**
|
|
167
|
-
* Subscribe to a slug's stream
|
|
166
|
+
* Subscribe to a slug's stream — **state is observed by slug**.
|
|
168
167
|
*
|
|
169
168
|
* Which kinds are subscribable, and why it is not a matter of taste:
|
|
170
169
|
*
|
|
@@ -203,7 +202,7 @@ export declare const clientUnsubscribe: z.ZodObject<{
|
|
|
203
202
|
export type ClientUnsubscribe = z.infer<typeof clientUnsubscribe>;
|
|
204
203
|
/**
|
|
205
204
|
* Refusal of a subscribe, addressed by the (robot_id, slug) it refers to.
|
|
206
|
-
* Codes follow the
|
|
205
|
+
* Codes follow the same error culture as REST: stable code plus human message.
|
|
207
206
|
* robot_id/slug are plain strings ECHOING what the client sent — the frame
|
|
208
207
|
* must be constructible precisely when those values are malformed, so that
|
|
209
208
|
* a bad robot_id or slug gets a diagnosis instead of a dead socket.
|
|
@@ -230,7 +229,7 @@ export declare const datapointEvent: z.ZodObject<{
|
|
|
230
229
|
}, z.core.$strip>;
|
|
231
230
|
export type DatapointEvent = z.infer<typeof datapointEvent>;
|
|
232
231
|
/**
|
|
233
|
-
* A change in the health of something the developer configured
|
|
232
|
+
* A change in the health of something the developer configured.
|
|
234
233
|
*
|
|
235
234
|
* The push half of `resourceHealthState`; the REST list is the snapshot half,
|
|
236
235
|
* and neither is useful alone — a page that loads after the change would see
|
|
@@ -241,19 +240,17 @@ export type DatapointEvent = z.infer<typeof datapointEvent>;
|
|
|
241
240
|
* looking at the thing that broke.
|
|
242
241
|
*/
|
|
243
242
|
/**
|
|
244
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Why a live camera session ended.
|
|
245
245
|
*
|
|
246
|
-
* **The reason travels WITH the ending,
|
|
247
|
-
*
|
|
248
|
-
* `
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* **sticky**, nothing moves a camera out of it, and `LiveCameraRow` read that
|
|
252
|
-
* *current* state at the moment a stream ended. A config change at 10:00 and
|
|
253
|
-
* an unrelated release at 10:30 therefore reported the same cause (DEF-070).
|
|
246
|
+
* **The reason travels WITH the ending, rather than being read afterwards from
|
|
247
|
+
* a state.** Some of these states are sticky — nothing moves a camera out of
|
|
248
|
+
* `stopped_by_config_change` — so a client that reads the current state at the
|
|
249
|
+
* moment a stream ends reports a configuration change from hours ago as the
|
|
250
|
+
* cause of an unrelated ending.
|
|
254
251
|
*
|
|
255
|
-
* A state read after the fact answers "what is true now". A viewer needs
|
|
256
|
-
*
|
|
252
|
+
* A state read after the fact answers "what is true now". A viewer needs "what
|
|
253
|
+
* happened to my session", and only an event carries that.
|
|
257
254
|
*/
|
|
258
255
|
export declare const liveSessionEndReason: z.ZodEnum<{
|
|
259
256
|
unknown: "unknown";
|
|
@@ -267,21 +264,18 @@ export declare const liveSessionEndReason: z.ZodEnum<{
|
|
|
267
264
|
}>;
|
|
268
265
|
export type LiveSessionEndReason = z.infer<typeof liveSessionEndReason>;
|
|
269
266
|
/**
|
|
270
|
-
|
|
267
|
+
/**
|
|
268
|
+
* A live camera session ended, told to the **client that holds it**.
|
|
271
269
|
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* reads in exactly one place — refusing a *later* joiner — so reporting a
|
|
276
|
-
* failure would have written to a dead end.
|
|
270
|
+
* Without this frame the only vehicle is `camera_state`, which the cloud stores
|
|
271
|
+
* and reads in exactly one place — refusing a *later* joiner — so a failure
|
|
272
|
+
* reported through it is written to a dead end.
|
|
277
273
|
*
|
|
278
274
|
* Unlike `resourceHealthEvent`, which is developer-only and org-scoped, this
|
|
279
|
-
* one is addressed to the **holder of the session**: it names `session_id`
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* the viewer learns about *their own session*. Two questions, two channels,
|
|
284
|
-
* on purpose.
|
|
275
|
+
* one is addressed to the **holder of the session**: it names `session_id` and
|
|
276
|
+
* is delivered only to the identity that session was minted for. A developer
|
|
277
|
+
* watching the same robot learns about the *resource* health; the viewer learns
|
|
278
|
+
* about *their own session*. Two questions, two channels, on purpose.
|
|
285
279
|
*/
|
|
286
280
|
export declare const liveSessionEvent: z.ZodObject<{
|
|
287
281
|
type: z.ZodLiteral<"live_session">;
|
|
@@ -304,16 +298,15 @@ export declare const liveSessionEvent: z.ZodObject<{
|
|
|
304
298
|
}, z.core.$strip>;
|
|
305
299
|
export type LiveSessionEvent = z.infer<typeof liveSessionEvent>;
|
|
306
300
|
/**
|
|
307
|
-
* A resource's health entry was **withdrawn
|
|
301
|
+
* A resource's health entry was **withdrawn**.
|
|
308
302
|
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* clearing exists for — leaves an open tab showing the old value indefinitely.
|
|
303
|
+
* Withdrawing a claim nobody can currently stand behind looks like it needs no
|
|
304
|
+
* event: the next `GET` already reflects it. That holds for a page that loads
|
|
305
|
+
* later. **It is false for a page that is already open, because there is no
|
|
306
|
+
* next `GET`** — a client fetches the snapshot once and then only ever writes
|
|
307
|
+
* keys the event stream gives it. A camera retargeted to a source that never
|
|
308
|
+
* reports, which is the case the clearing exists for, would leave an open tab
|
|
309
|
+
* showing the old value indefinitely.
|
|
317
310
|
*
|
|
318
311
|
* **A separate event type rather than a nullable `state` on the existing
|
|
319
312
|
* one**, so a consumer's `switch` has to name it. A nullable field invites
|
|
@@ -360,17 +353,14 @@ export declare const resourceHealthEvent: z.ZodObject<{
|
|
|
360
353
|
}, z.core.$strip>;
|
|
361
354
|
export type ResourceHealthEvent = z.infer<typeof resourceHealthEvent>;
|
|
362
355
|
/**
|
|
363
|
-
* One line of the developer console's activity panel
|
|
364
|
-
* `2026-08-20-org-event-stream`).
|
|
356
|
+
* One line of the developer console's activity panel.
|
|
365
357
|
*
|
|
366
358
|
* **This is an activity log for humans, not a complete feed.** It is throttled
|
|
367
|
-
* and sampled. `orgEventDropped`
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* protocol. Anything that needs completeness reads the audit log or the
|
|
373
|
-
* job-run history, both durable, both 90 days.
|
|
359
|
+
* and sampled. `orgEventDropped` reports a drop on the wire, but no Fleetless
|
|
360
|
+
* surface renders it, so a reader of this stream cannot tell a complete window
|
|
361
|
+
* from a sampled one unless their own client shows the drop. Anything that
|
|
362
|
+
* needs completeness reads the audit log or the job-run history, both durable,
|
|
363
|
+
* both 90 days.
|
|
374
364
|
*/
|
|
375
365
|
export declare const ORG_EVENT_SAMPLE_INTERVAL_MS = 1000;
|
|
376
366
|
/** The backstop above the per-slug cap: a fleet larger than the panel could serve anyway. */
|
|
@@ -382,21 +372,16 @@ export declare const ORG_EVENT_BUFFER_IDLE_MS = 3600000;
|
|
|
382
372
|
/** A log line, not a payload: a datapoint value is `unknown` and a LaserScan is megabytes. */
|
|
383
373
|
export declare const ORG_EVENT_DETAIL_MAX_BYTES = 4096;
|
|
384
374
|
/**
|
|
385
|
-
* `'alert'` — a transition of a datapoint alert (`ok ⇄ firing
|
|
386
|
-
* `
|
|
387
|
-
*
|
|
388
|
-
* is good news regardless of how bad the firing was.
|
|
375
|
+
* `'alert'` — a transition of a datapoint alert (`ok ⇄ firing`). A firing
|
|
376
|
+
* event carries the alert's own `severity`; a resolved event is always `info` —
|
|
377
|
+
* resolving is good news regardless of how bad the firing was.
|
|
389
378
|
*
|
|
390
|
-
* `'datapoint'` —
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
* able to parse it rather than fail closed on an old, valid value. Same shape
|
|
397
|
-
* as the correction on `ORG_EVENT_SAMPLE_INTERVAL_MS`'s comment just above:
|
|
398
|
-
* name what the wire no longer does instead of leaving a value the cloud can
|
|
399
|
-
* never send undocumented.
|
|
379
|
+
* `'datapoint'` — **no producer emits this kind**: per-slug sampling on this
|
|
380
|
+
* stream made it redundant with what the datapoint history route already
|
|
381
|
+
* serves. The member stays in the enum rather than being removed, because a
|
|
382
|
+
* reader may still hold an older frame of this kind in a buffer (a reconnect
|
|
383
|
+
* replay, a client that has not refreshed) and must be able to parse it rather
|
|
384
|
+
* than fail closed on an old, valid value.
|
|
400
385
|
*/
|
|
401
386
|
export declare const orgEventKind: z.ZodEnum<{
|
|
402
387
|
datapoint: "datapoint";
|
package/dist/realtime.js
CHANGED
|
@@ -6,12 +6,11 @@ import { slug } from './common.js';
|
|
|
6
6
|
import { clientIdentity } from './client-auth.js';
|
|
7
7
|
import { job } from './jobs.js';
|
|
8
8
|
/**
|
|
9
|
-
* Client realtime protocol
|
|
10
|
-
*
|
|
11
|
-
* stream; command parity arrives in W4.
|
|
9
|
+
* Client realtime protocol: WebSocket subscriptions on datapoints, the
|
|
10
|
+
* datapoint event stream, and full command parity with REST.
|
|
12
11
|
*/
|
|
13
12
|
/**
|
|
14
|
-
* The first frame a client sends after the socket opens
|
|
13
|
+
* The first frame a client sends after the socket opens.
|
|
15
14
|
*
|
|
16
15
|
* A browser cannot set an `Authorization` header on a WebSocket handshake,
|
|
17
16
|
* and a token in the query string would outlive the request in server,
|
|
@@ -43,7 +42,7 @@ export const authError = z.object({
|
|
|
43
42
|
message: z.string().min(1),
|
|
44
43
|
});
|
|
45
44
|
/**
|
|
46
|
-
* Command parity
|
|
45
|
+
* Command parity: everything REST can do — invoke an action,
|
|
47
46
|
* call a service, publish, cancel — also travels over this socket.
|
|
48
47
|
*
|
|
49
48
|
* **Every command carries a `request_id` and every reply echoes it.** A
|
|
@@ -58,20 +57,16 @@ export const clientInvoke = z.object({
|
|
|
58
57
|
request_id: z.string().min(1).max(64),
|
|
59
58
|
robot_id: z.uuid(),
|
|
60
59
|
slug,
|
|
61
|
-
/** Parameters by field path, validated against the
|
|
60
|
+
/** Parameters by field path, validated against the configuration's rules. */
|
|
62
61
|
params: z.record(z.string(), z.unknown()),
|
|
63
62
|
/**
|
|
64
|
-
* How long this one call is worth waiting for
|
|
65
|
-
*
|
|
66
|
-
* `DEFAULT_PATIENCE_MS`.
|
|
63
|
+
* How long this one call is worth waiting for — the same field, meaning and
|
|
64
|
+
* cap as `invokeRequest.patience_ms`; absent means `DEFAULT_PATIENCE_MS`.
|
|
67
65
|
*
|
|
68
|
-
* It is here because
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* SDK caller while appearing in the documentation. W6a shipped four SDK
|
|
73
|
-
* methods no SDK caller could invoke; this is the same defect caught before
|
|
74
|
-
* it shipped, by the SDK owner rather than by a reviewer.
|
|
66
|
+
* It is here because **parity is a rule, not a preference**: what REST can do
|
|
67
|
+
* travels over this socket. A field given to the REST body alone would be
|
|
68
|
+
* unreachable to every caller that invokes over the realtime channel, while
|
|
69
|
+
* still appearing in the documentation.
|
|
75
70
|
*/
|
|
76
71
|
patience_ms: z.number().int().min(MIN_PATIENCE_MS).max(MAX_PATIENCE_MS).optional(),
|
|
77
72
|
});
|
|
@@ -79,17 +74,17 @@ export const clientCancel = z.object({
|
|
|
79
74
|
type: z.literal('cancel'),
|
|
80
75
|
request_id: z.string().min(1).max(64),
|
|
81
76
|
robot_id: z.uuid(),
|
|
82
|
-
/** Which slug — required, and the
|
|
77
|
+
/** Which slug — required, and the coarse address of a cancel. */
|
|
83
78
|
slug,
|
|
84
79
|
/**
|
|
85
|
-
* Which job on that slug
|
|
80
|
+
* Which job on that slug, or `null` for *whatever is running there*.
|
|
86
81
|
*
|
|
87
82
|
* The two are different requests and both are legitimate. An operator
|
|
88
83
|
* hitting a stop button means the second: stop the machine, whatever it is
|
|
89
84
|
* doing. A client cancelling the job it started means the first — and until
|
|
90
|
-
* this field
|
|
91
|
-
*
|
|
92
|
-
*
|
|
85
|
+
* this field a client could not say so, and a cancel arriving just after its
|
|
86
|
+
* own job ended would stop the next caller's job instead. Same slug, same
|
|
87
|
+
* wire frame, entirely different machine behaviour, and nothing in the
|
|
93
88
|
* protocol able to tell them apart.
|
|
94
89
|
*
|
|
95
90
|
* A named id that is not running answers `not_found` rather than falling
|
|
@@ -121,11 +116,10 @@ export const clientPublish = z.object({
|
|
|
121
116
|
* a command has reached the bridge, deliberately, so that the next command —
|
|
122
117
|
* a stop, say — is never held up behind bookkeeping. Work that follows the
|
|
123
118
|
* send therefore runs unordered: a publish that *acquires* a slug writes an
|
|
124
|
-
* audit record before answering, while an immediately following publish by
|
|
125
|
-
*
|
|
126
|
-
* overtakes.
|
|
127
|
-
*
|
|
128
|
-
* identity and slug — and not of a race.
|
|
119
|
+
* audit record before answering, while an immediately following publish by the
|
|
120
|
+
* now-current holder has nothing to write and answers at once. Its reply
|
|
121
|
+
* overtakes. That reversal happens at most once per identity and slug, which
|
|
122
|
+
* is what distinguishes it from a race.
|
|
129
123
|
*
|
|
130
124
|
* Serialising the replies would mean putting that bookkeeping in front of
|
|
131
125
|
* every following command, including the stop. The ordering that matters is
|
|
@@ -141,9 +135,9 @@ export const commandResult = z.object({
|
|
|
141
135
|
* - `ok:true` on an invoke or a call: the job that was just created.
|
|
142
136
|
* - `ok:true` on a cancel: the job the cancel was sent to.
|
|
143
137
|
* - `ok:false, code:'busy'`: **the job that is already running** — the
|
|
144
|
-
* caller has none.
|
|
145
|
-
*
|
|
146
|
-
*
|
|
138
|
+
* caller has none. Naming what is already running is the whole reason a
|
|
139
|
+
* busy refusal is useful: the caller learns whether to wait or to give up
|
|
140
|
+
* (see `busyDetails`).
|
|
147
141
|
* - any other refusal: `null`.
|
|
148
142
|
*/
|
|
149
143
|
job: job.nullable(),
|
|
@@ -165,12 +159,11 @@ export const commandResult = z.object({
|
|
|
165
159
|
* The same payload the REST envelope carries in `apiError.details` — for
|
|
166
160
|
* `parameter_invalid`, a `parameterInvalidDetails`.
|
|
167
161
|
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* alone — which is the entire point of the flat parameter shape.
|
|
162
|
+
* It is here because this socket does *everything* REST can do, and without
|
|
163
|
+
* it a `parameter_invalid` arriving here would have nowhere to put its
|
|
164
|
+
* violations — the same refusal actionable over HTTP and opaque over the
|
|
165
|
+
* socket. A client cannot bind an error to the input that caused it from a
|
|
166
|
+
* code alone, which is the entire point of the flat parameter shape.
|
|
174
167
|
*/
|
|
175
168
|
details: z.unknown().optional(),
|
|
176
169
|
});
|
|
@@ -187,7 +180,7 @@ export const errorFrame = z.object({
|
|
|
187
180
|
message: z.string().min(1),
|
|
188
181
|
});
|
|
189
182
|
/**
|
|
190
|
-
* Subscribe to a slug's stream
|
|
183
|
+
* Subscribe to a slug's stream — **state is observed by slug**.
|
|
191
184
|
*
|
|
192
185
|
* Which kinds are subscribable, and why it is not a matter of taste:
|
|
193
186
|
*
|
|
@@ -209,7 +202,7 @@ export const clientSubscribe = z.object({
|
|
|
209
202
|
robot_id: z.uuid(),
|
|
210
203
|
slug,
|
|
211
204
|
/**
|
|
212
|
-
* What the subscriber expects, and how it wants it
|
|
205
|
+
* What the subscriber expects, and how it wants it.
|
|
213
206
|
*
|
|
214
207
|
* `kind` lets the server answer **`wrong_kind`** instead of accepting a
|
|
215
208
|
* subscribe the client will then filter to silence — and silence is
|
|
@@ -217,8 +210,6 @@ export const clientSubscribe = z.object({
|
|
|
217
210
|
* Optional, so an older client that omits it keeps today's behaviour.
|
|
218
211
|
*
|
|
219
212
|
* `options` is where a camera says what it wants; a datapoint needs none.
|
|
220
|
-
* It exists now rather than later because adding a field to a frame three
|
|
221
|
-
* repos parse is cheap once and expensive twice.
|
|
222
213
|
*/
|
|
223
214
|
/**
|
|
224
215
|
* `publisher` is here even though a publisher is not subscribable: a client
|
|
@@ -238,7 +229,7 @@ export const clientUnsubscribe = z.object({
|
|
|
238
229
|
});
|
|
239
230
|
/**
|
|
240
231
|
* Refusal of a subscribe, addressed by the (robot_id, slug) it refers to.
|
|
241
|
-
* Codes follow the
|
|
232
|
+
* Codes follow the same error culture as REST: stable code plus human message.
|
|
242
233
|
* robot_id/slug are plain strings ECHOING what the client sent — the frame
|
|
243
234
|
* must be constructible precisely when those values are malformed, so that
|
|
244
235
|
* a bad robot_id or slug gets a diagnosis instead of a dead socket.
|
|
@@ -263,7 +254,7 @@ export const datapointEvent = z.object({
|
|
|
263
254
|
timestamp_ms: z.number().int().nonnegative(),
|
|
264
255
|
});
|
|
265
256
|
/**
|
|
266
|
-
* A change in the health of something the developer configured
|
|
257
|
+
* A change in the health of something the developer configured.
|
|
267
258
|
*
|
|
268
259
|
* The push half of `resourceHealthState`; the REST list is the snapshot half,
|
|
269
260
|
* and neither is useful alone — a page that loads after the change would see
|
|
@@ -274,19 +265,17 @@ export const datapointEvent = z.object({
|
|
|
274
265
|
* looking at the thing that broke.
|
|
275
266
|
*/
|
|
276
267
|
/**
|
|
277
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Why a live camera session ended.
|
|
278
270
|
*
|
|
279
|
-
* **The reason travels WITH the ending,
|
|
280
|
-
*
|
|
281
|
-
* `
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
* **sticky**, nothing moves a camera out of it, and `LiveCameraRow` read that
|
|
285
|
-
* *current* state at the moment a stream ended. A config change at 10:00 and
|
|
286
|
-
* an unrelated release at 10:30 therefore reported the same cause (DEF-070).
|
|
271
|
+
* **The reason travels WITH the ending, rather than being read afterwards from
|
|
272
|
+
* a state.** Some of these states are sticky — nothing moves a camera out of
|
|
273
|
+
* `stopped_by_config_change` — so a client that reads the current state at the
|
|
274
|
+
* moment a stream ends reports a configuration change from hours ago as the
|
|
275
|
+
* cause of an unrelated ending.
|
|
287
276
|
*
|
|
288
|
-
* A state read after the fact answers "what is true now". A viewer needs
|
|
289
|
-
*
|
|
277
|
+
* A state read after the fact answers "what is true now". A viewer needs "what
|
|
278
|
+
* happened to my session", and only an event carries that.
|
|
290
279
|
*/
|
|
291
280
|
export const liveSessionEndReason = z.enum([
|
|
292
281
|
/** Another holder of this camera released it — another tab, or another client. */
|
|
@@ -312,21 +301,18 @@ export const liveSessionEndReason = z.enum([
|
|
|
312
301
|
'unknown',
|
|
313
302
|
]);
|
|
314
303
|
/**
|
|
315
|
-
|
|
304
|
+
/**
|
|
305
|
+
* A live camera session ended, told to the **client that holds it**.
|
|
316
306
|
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
* reads in exactly one place — refusing a *later* joiner — so reporting a
|
|
321
|
-
* failure would have written to a dead end.
|
|
307
|
+
* Without this frame the only vehicle is `camera_state`, which the cloud stores
|
|
308
|
+
* and reads in exactly one place — refusing a *later* joiner — so a failure
|
|
309
|
+
* reported through it is written to a dead end.
|
|
322
310
|
*
|
|
323
311
|
* Unlike `resourceHealthEvent`, which is developer-only and org-scoped, this
|
|
324
|
-
* one is addressed to the **holder of the session**: it names `session_id`
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* the viewer learns about *their own session*. Two questions, two channels,
|
|
329
|
-
* on purpose.
|
|
312
|
+
* one is addressed to the **holder of the session**: it names `session_id` and
|
|
313
|
+
* is delivered only to the identity that session was minted for. A developer
|
|
314
|
+
* watching the same robot learns about the *resource* health; the viewer learns
|
|
315
|
+
* about *their own session*. Two questions, two channels, on purpose.
|
|
330
316
|
*/
|
|
331
317
|
export const liveSessionEvent = z.object({
|
|
332
318
|
type: z.literal('live_session'),
|
|
@@ -338,13 +324,10 @@ export const liveSessionEvent = z.object({
|
|
|
338
324
|
/**
|
|
339
325
|
* **Classified text the cloud produced, never text the robot sent.**
|
|
340
326
|
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
* exactly that route — `camera-health.ts`'s fixed-string `REASON` discipline
|
|
346
|
-
* exists because of it. Nimbus-W9a stopped at the sentence and asked rather
|
|
347
|
-
* than taking the permission it appeared to give (2026-08-19).
|
|
327
|
+
* It is **not** the robot's own words. Nothing sanitises
|
|
328
|
+
* `bridgeCameraState.error.message`, and a camera password reaches a
|
|
329
|
+
* developer surface through exactly that route — which is why the cloud maps
|
|
330
|
+
* a robot's diagnosis to fixed strings rather than forwarding it.
|
|
348
331
|
*
|
|
349
332
|
* So: `null` unless the cloud itself has something classified to say. If a
|
|
350
333
|
* developer needs the robot's own diagnosis later, it arrives as a mapped
|
|
@@ -356,16 +339,15 @@ export const liveSessionEvent = z.object({
|
|
|
356
339
|
ended_at_ms: z.number().int().nonnegative(),
|
|
357
340
|
});
|
|
358
341
|
/**
|
|
359
|
-
* A resource's health entry was **withdrawn
|
|
342
|
+
* A resource's health entry was **withdrawn**.
|
|
360
343
|
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
* clearing exists for — leaves an open tab showing the old value indefinitely.
|
|
344
|
+
* Withdrawing a claim nobody can currently stand behind looks like it needs no
|
|
345
|
+
* event: the next `GET` already reflects it. That holds for a page that loads
|
|
346
|
+
* later. **It is false for a page that is already open, because there is no
|
|
347
|
+
* next `GET`** — a client fetches the snapshot once and then only ever writes
|
|
348
|
+
* keys the event stream gives it. A camera retargeted to a source that never
|
|
349
|
+
* reports, which is the case the clearing exists for, would leave an open tab
|
|
350
|
+
* showing the old value indefinitely.
|
|
369
351
|
*
|
|
370
352
|
* **A separate event type rather than a nullable `state` on the existing
|
|
371
353
|
* one**, so a consumer's `switch` has to name it. A nullable field invites
|
|
@@ -392,17 +374,14 @@ export const resourceHealthEvent = z.object({
|
|
|
392
374
|
changed_at_ms: z.number().int().nonnegative(),
|
|
393
375
|
});
|
|
394
376
|
/**
|
|
395
|
-
* One line of the developer console's activity panel
|
|
396
|
-
* `2026-08-20-org-event-stream`).
|
|
377
|
+
* One line of the developer console's activity panel.
|
|
397
378
|
*
|
|
398
379
|
* **This is an activity log for humans, not a complete feed.** It is throttled
|
|
399
|
-
* and sampled. `orgEventDropped`
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
* protocol. Anything that needs completeness reads the audit log or the
|
|
405
|
-
* job-run history, both durable, both 90 days.
|
|
380
|
+
* and sampled. `orgEventDropped` reports a drop on the wire, but no Fleetless
|
|
381
|
+
* surface renders it, so a reader of this stream cannot tell a complete window
|
|
382
|
+
* from a sampled one unless their own client shows the drop. Anything that
|
|
383
|
+
* needs completeness reads the audit log or the job-run history, both durable,
|
|
384
|
+
* both 90 days.
|
|
406
385
|
*/
|
|
407
386
|
export const ORG_EVENT_SAMPLE_INTERVAL_MS = 1_000;
|
|
408
387
|
/** The backstop above the per-slug cap: a fleet larger than the panel could serve anyway. */
|
|
@@ -414,21 +393,16 @@ export const ORG_EVENT_BUFFER_IDLE_MS = 3_600_000;
|
|
|
414
393
|
/** A log line, not a payload: a datapoint value is `unknown` and a LaserScan is megabytes. */
|
|
415
394
|
export const ORG_EVENT_DETAIL_MAX_BYTES = 4_096;
|
|
416
395
|
/**
|
|
417
|
-
* `'alert'` — a transition of a datapoint alert (`ok ⇄ firing
|
|
418
|
-
* `
|
|
419
|
-
*
|
|
420
|
-
* is good news regardless of how bad the firing was.
|
|
396
|
+
* `'alert'` — a transition of a datapoint alert (`ok ⇄ firing`). A firing
|
|
397
|
+
* event carries the alert's own `severity`; a resolved event is always `info` —
|
|
398
|
+
* resolving is good news regardless of how bad the firing was.
|
|
421
399
|
*
|
|
422
|
-
* `'datapoint'` —
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
* able to parse it rather than fail closed on an old, valid value. Same shape
|
|
429
|
-
* as the correction on `ORG_EVENT_SAMPLE_INTERVAL_MS`'s comment just above:
|
|
430
|
-
* name what the wire no longer does instead of leaving a value the cloud can
|
|
431
|
-
* never send undocumented.
|
|
400
|
+
* `'datapoint'` — **no producer emits this kind**: per-slug sampling on this
|
|
401
|
+
* stream made it redundant with what the datapoint history route already
|
|
402
|
+
* serves. The member stays in the enum rather than being removed, because a
|
|
403
|
+
* reader may still hold an older frame of this kind in a buffer (a reconnect
|
|
404
|
+
* replay, a client that has not refreshed) and must be able to parse it rather
|
|
405
|
+
* than fail closed on an old, valid value.
|
|
432
406
|
*/
|
|
433
407
|
export const orgEventKind = z.enum(['datapoint', 'health', 'job', 'bridge', 'audit', 'alert']);
|
|
434
408
|
/**
|