@fleetless/contracts 4.0.0-next.1 → 5.0.0-next.1
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 +35 -5
- package/artifacts/constants.json +4 -14
- package/artifacts/openapi.json +46 -13
- package/artifacts/routes.json +2 -2
- package/artifacts/schema/bridge-hello.schema.json +1 -0
- package/artifacts/schema/bridge-job-status.schema.json +114 -0
- package/artifacts/schema/bridge-job-update.schema.json +21 -0
- package/artifacts/schema/busy-details.schema.json +12 -2
- package/artifacts/schema/cloud-job-query.schema.json +29 -0
- package/artifacts/schema/command-result.schema.json +12 -2
- package/artifacts/schema/invoke-or-service-response.schema.json +12 -2
- package/artifacts/schema/invoke-response.schema.json +12 -2
- package/artifacts/schema/job-event.schema.json +12 -2
- package/artifacts/schema/job-response.schema.json +12 -2
- package/artifacts/schema/job-run-list-response.schema.json +4 -3
- package/artifacts/schema/job-run-query.schema.json +2 -1
- package/artifacts/schema/job-run.schema.json +4 -3
- package/artifacts/schema/job-state.schema.json +1 -0
- package/artifacts/schema/job.schema.json +12 -2
- package/artifacts/schema/robot-jobs-response.schema.json +12 -2
- package/artifacts/schema-outgoing/bridge-hello.schema.json +1 -0
- package/artifacts/schema-outgoing/bridge-job-status.schema.json +117 -0
- package/artifacts/schema-outgoing/bridge-job-update.schema.json +21 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +39 -8
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/jobs.d.ts +55 -6
- package/dist/jobs.js +43 -14
- package/dist/protocol.d.ts +165 -44
- package/dist/protocol.js +144 -59
- package/dist/realtime.d.ts +5 -0
- package/dist/rest.d.ts +20 -0
- package/dist/routes.js +6 -3
- package/package.json +1 -1
package/dist/jobs.d.ts
CHANGED
|
@@ -10,14 +10,20 @@ import { z } from 'zod';
|
|
|
10
10
|
* 1. **State is observed by slug, not by id.** The id is informative; a client
|
|
11
11
|
* watches `robot × slug` and sees whatever job is running there, which is
|
|
12
12
|
* also why every observer of a slug sees the same job.
|
|
13
|
-
* 2.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* 2. **What the cloud does not know it calls `unknown`, and `lost` is final.**
|
|
14
|
+
* A job whose robot went quiet — offline past `JOB_OFFLINE_GRACE_MS`, or
|
|
15
|
+
* connected but silent past `JOB_HEARTBEAT_TIMEOUT_MS` — is `unknown`: not
|
|
16
|
+
* terminal, the slug stays occupied, and only a statement of the bridge
|
|
17
|
+
* resolves it (it is running, it ended, or the bridge does not know it and
|
|
18
|
+
* nothing else runs on its action). `lost` is what that last statement
|
|
19
|
+
* produces, and nothing ever leaves it. Neither is left reading "running"
|
|
20
|
+
* because nobody contradicted it: a system that reports a machine is still
|
|
21
|
+
* working when it does not know is worse than one that says so — and one
|
|
22
|
+
* that declares work lost on a guess is wrong the moment the robot comes
|
|
23
|
+
* back and says it finished.
|
|
19
24
|
*/
|
|
20
25
|
export declare const jobState: z.ZodEnum<{
|
|
26
|
+
unknown: "unknown";
|
|
21
27
|
failed: "failed";
|
|
22
28
|
running: "running";
|
|
23
29
|
succeeded: "succeeded";
|
|
@@ -25,17 +31,42 @@ export declare const jobState: z.ZodEnum<{
|
|
|
25
31
|
lost: "lost";
|
|
26
32
|
}>;
|
|
27
33
|
export type JobState = z.infer<typeof jobState>;
|
|
34
|
+
/**
|
|
35
|
+
* Who started a job.
|
|
36
|
+
*
|
|
37
|
+
* `fleetless` for every job the cloud minted from an invocation. `external`
|
|
38
|
+
* for a goal the bridge found active on a published action without having
|
|
39
|
+
* sent it — started by anyone else on the robot's ROS graph, or the bridge's
|
|
40
|
+
* own goal after its mapping was lost. An external job has the same shape,
|
|
41
|
+
* states, live stream and cancel as any other, but no parameters (ROS 2
|
|
42
|
+
* publishes a goal's request nowhere), no starter, and it lives in memory
|
|
43
|
+
* only: it is never written to `job_runs` and never counts towards quotas.
|
|
44
|
+
*/
|
|
45
|
+
export declare const jobOrigin: z.ZodEnum<{
|
|
46
|
+
fleetless: "fleetless";
|
|
47
|
+
external: "external";
|
|
48
|
+
}>;
|
|
49
|
+
export type JobOrigin = z.infer<typeof jobOrigin>;
|
|
50
|
+
/**
|
|
51
|
+
* One job, as the cloud tells every client about it — a Fleetless job or an
|
|
52
|
+
* external goal alike, told apart only by `origin`.
|
|
53
|
+
*/
|
|
28
54
|
export declare const job: z.ZodObject<{
|
|
29
55
|
id: z.ZodUUID;
|
|
30
56
|
robot_id: z.ZodUUID;
|
|
31
57
|
slug: z.ZodString;
|
|
32
58
|
state: z.ZodEnum<{
|
|
59
|
+
unknown: "unknown";
|
|
33
60
|
failed: "failed";
|
|
34
61
|
running: "running";
|
|
35
62
|
succeeded: "succeeded";
|
|
36
63
|
cancelled: "cancelled";
|
|
37
64
|
lost: "lost";
|
|
38
65
|
}>;
|
|
66
|
+
origin: z.ZodEnum<{
|
|
67
|
+
fleetless: "fleetless";
|
|
68
|
+
external: "external";
|
|
69
|
+
}>;
|
|
39
70
|
started_at: z.ZodISODateTime;
|
|
40
71
|
updated_at: z.ZodISODateTime;
|
|
41
72
|
seq: z.ZodNumber;
|
|
@@ -65,12 +96,17 @@ export declare const jobEvent: z.ZodObject<{
|
|
|
65
96
|
robot_id: z.ZodUUID;
|
|
66
97
|
slug: z.ZodString;
|
|
67
98
|
state: z.ZodEnum<{
|
|
99
|
+
unknown: "unknown";
|
|
68
100
|
failed: "failed";
|
|
69
101
|
running: "running";
|
|
70
102
|
succeeded: "succeeded";
|
|
71
103
|
cancelled: "cancelled";
|
|
72
104
|
lost: "lost";
|
|
73
105
|
}>;
|
|
106
|
+
origin: z.ZodEnum<{
|
|
107
|
+
fleetless: "fleetless";
|
|
108
|
+
external: "external";
|
|
109
|
+
}>;
|
|
74
110
|
started_at: z.ZodISODateTime;
|
|
75
111
|
updated_at: z.ZodISODateTime;
|
|
76
112
|
seq: z.ZodNumber;
|
|
@@ -89,6 +125,11 @@ export type JobEvent = z.infer<typeof jobEvent>;
|
|
|
89
125
|
/**
|
|
90
126
|
* What a busy refusal tells the caller: what is already running. A refusal that
|
|
91
127
|
* only says "busy" forces the caller to guess whether to wait or to give up.
|
|
128
|
+
*
|
|
129
|
+
* `running` is whatever occupies the slug — a `running` job, an `unknown` one
|
|
130
|
+
* the robot has not accounted for yet, or an `external` goal someone else
|
|
131
|
+
* started — and its `state` and `origin` say which, so a caller can tell
|
|
132
|
+
* "wait for it" from "cancel what someone else started".
|
|
92
133
|
*/
|
|
93
134
|
export declare const busyDetails: z.ZodObject<{
|
|
94
135
|
running: z.ZodObject<{
|
|
@@ -96,12 +137,17 @@ export declare const busyDetails: z.ZodObject<{
|
|
|
96
137
|
robot_id: z.ZodUUID;
|
|
97
138
|
slug: z.ZodString;
|
|
98
139
|
state: z.ZodEnum<{
|
|
140
|
+
unknown: "unknown";
|
|
99
141
|
failed: "failed";
|
|
100
142
|
running: "running";
|
|
101
143
|
succeeded: "succeeded";
|
|
102
144
|
cancelled: "cancelled";
|
|
103
145
|
lost: "lost";
|
|
104
146
|
}>;
|
|
147
|
+
origin: z.ZodEnum<{
|
|
148
|
+
fleetless: "fleetless";
|
|
149
|
+
external: "external";
|
|
150
|
+
}>;
|
|
105
151
|
started_at: z.ZodISODateTime;
|
|
106
152
|
updated_at: z.ZodISODateTime;
|
|
107
153
|
seq: z.ZodNumber;
|
|
@@ -203,6 +249,7 @@ export declare const jobRun: z.ZodObject<{
|
|
|
203
249
|
service: "service";
|
|
204
250
|
}>;
|
|
205
251
|
state: z.ZodEnum<{
|
|
252
|
+
unknown: "unknown";
|
|
206
253
|
failed: "failed";
|
|
207
254
|
running: "running";
|
|
208
255
|
succeeded: "succeeded";
|
|
@@ -239,6 +286,7 @@ export declare const jobRunQuery: z.ZodObject<{
|
|
|
239
286
|
robot_id: z.ZodOptional<z.ZodUUID>;
|
|
240
287
|
slug: z.ZodOptional<z.ZodString>;
|
|
241
288
|
state: z.ZodOptional<z.ZodEnum<{
|
|
289
|
+
unknown: "unknown";
|
|
242
290
|
failed: "failed";
|
|
243
291
|
running: "running";
|
|
244
292
|
succeeded: "succeeded";
|
|
@@ -263,6 +311,7 @@ export declare const jobRunListResponse: z.ZodObject<{
|
|
|
263
311
|
service: "service";
|
|
264
312
|
}>;
|
|
265
313
|
state: z.ZodEnum<{
|
|
314
|
+
unknown: "unknown";
|
|
266
315
|
failed: "failed";
|
|
267
316
|
running: "running";
|
|
268
317
|
succeeded: "succeeded";
|
package/dist/jobs.js
CHANGED
|
@@ -11,14 +11,35 @@ import { slug, wireSeqCursor, wireTimestampMs } from './common.js';
|
|
|
11
11
|
* 1. **State is observed by slug, not by id.** The id is informative; a client
|
|
12
12
|
* watches `robot × slug` and sees whatever job is running there, which is
|
|
13
13
|
* also why every observer of a slug sees the same job.
|
|
14
|
-
* 2.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* 2. **What the cloud does not know it calls `unknown`, and `lost` is final.**
|
|
15
|
+
* A job whose robot went quiet — offline past `JOB_OFFLINE_GRACE_MS`, or
|
|
16
|
+
* connected but silent past `JOB_HEARTBEAT_TIMEOUT_MS` — is `unknown`: not
|
|
17
|
+
* terminal, the slug stays occupied, and only a statement of the bridge
|
|
18
|
+
* resolves it (it is running, it ended, or the bridge does not know it and
|
|
19
|
+
* nothing else runs on its action). `lost` is what that last statement
|
|
20
|
+
* produces, and nothing ever leaves it. Neither is left reading "running"
|
|
21
|
+
* because nobody contradicted it: a system that reports a machine is still
|
|
22
|
+
* working when it does not know is worse than one that says so — and one
|
|
23
|
+
* that declares work lost on a guess is wrong the moment the robot comes
|
|
24
|
+
* back and says it finished.
|
|
25
|
+
*/
|
|
26
|
+
export const jobState = z.enum(['running', 'unknown', 'succeeded', 'failed', 'cancelled', 'lost']);
|
|
27
|
+
/**
|
|
28
|
+
* Who started a job.
|
|
29
|
+
*
|
|
30
|
+
* `fleetless` for every job the cloud minted from an invocation. `external`
|
|
31
|
+
* for a goal the bridge found active on a published action without having
|
|
32
|
+
* sent it — started by anyone else on the robot's ROS graph, or the bridge's
|
|
33
|
+
* own goal after its mapping was lost. An external job has the same shape,
|
|
34
|
+
* states, live stream and cancel as any other, but no parameters (ROS 2
|
|
35
|
+
* publishes a goal's request nowhere), no starter, and it lives in memory
|
|
36
|
+
* only: it is never written to `job_runs` and never counts towards quotas.
|
|
37
|
+
*/
|
|
38
|
+
export const jobOrigin = z.enum(['fleetless', 'external']);
|
|
39
|
+
/**
|
|
40
|
+
* One job, as the cloud tells every client about it — a Fleetless job or an
|
|
41
|
+
* external goal alike, told apart only by `origin`.
|
|
20
42
|
*/
|
|
21
|
-
export const jobState = z.enum(['running', 'succeeded', 'failed', 'cancelled', 'lost']);
|
|
22
43
|
export const job = z.object({
|
|
23
44
|
id: z.uuid().meta({
|
|
24
45
|
description: 'The job\'s id, minted by the cloud when the invocation is accepted. Informative — state is observed by slug; a cancel names this id to stop one specific job rather than whatever is running.',
|
|
@@ -28,7 +49,10 @@ export const job = z.object({
|
|
|
28
49
|
description: 'The action or service this job is running, as the published configuration exposes it. One slug carries one job at a time, so every observer of that slug sees the same one.',
|
|
29
50
|
}),
|
|
30
51
|
state: jobState.meta({
|
|
31
|
-
description: 'Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `
|
|
52
|
+
description: 'Where the job stands: `running`, `unknown`, `succeeded`, `failed`, `cancelled` or `lost`. `unknown` is not an outcome — the robot went offline or silent and the cloud does not know yet; the slug stays occupied and the bridge\'s next statement resolves it, `error` naming why the cloud lost sight of it. `lost` is final: the bridge stated it does not know the job and nothing else runs on its action, or the action server vanished mid-goal.',
|
|
53
|
+
}),
|
|
54
|
+
origin: jobOrigin.meta({
|
|
55
|
+
description: "Who started this job. `fleetless` for everything minted by the cloud; `external` for a goal the bridge found active on a published action without having sent it — no parameters, no starter, never written to `job_runs`.",
|
|
32
56
|
}),
|
|
33
57
|
started_at: z.iso.datetime().meta({
|
|
34
58
|
description: 'When the cloud minted this job, as an ISO 8601 timestamp. For a job adopted from a reconnecting bridge, this is **adoption time**, not the real start — the cloud never minted it.',
|
|
@@ -47,7 +71,7 @@ export const job = z.object({
|
|
|
47
71
|
* exists for the same reason on the audit log.
|
|
48
72
|
*
|
|
49
73
|
* **Scoped honestly: per cloud process, per run.** Job state lives in memory
|
|
50
|
-
* — that is why `lost`
|
|
74
|
+
* — that is why `unknown` and `lost` exist at all — so this counter restarts when
|
|
51
75
|
* the cloud does, alongside the jobs it orders. Sound, because it only ever
|
|
52
76
|
* orders jobs that coexist in one registry — and stated, because a reader
|
|
53
77
|
* who assumed `auditEvent.seq`'s durable semantics would be wrong.
|
|
@@ -85,7 +109,7 @@ export const job = z.object({
|
|
|
85
109
|
})
|
|
86
110
|
.nullable()
|
|
87
111
|
.meta({
|
|
88
|
-
description: 'Why the job failed: a human `message`, a `code` where one exists, and `details` for the codes that carry a documented payload. `
|
|
112
|
+
description: 'Why the job failed, or why the cloud does not know how it stands: a human `message`, a `code` where one exists, and `details` for the codes that carry a documented payload. Set on `failed` and `lost`, and on `unknown` — where `code` is `bridge_disconnected` or `bridge_timeout`, the cloud\'s own reason for not knowing, cleared when the bridge reports the job running again.',
|
|
89
113
|
}),
|
|
90
114
|
});
|
|
91
115
|
/**
|
|
@@ -111,6 +135,11 @@ export const jobEvent = z.object({
|
|
|
111
135
|
/**
|
|
112
136
|
* What a busy refusal tells the caller: what is already running. A refusal that
|
|
113
137
|
* only says "busy" forces the caller to guess whether to wait or to give up.
|
|
138
|
+
*
|
|
139
|
+
* `running` is whatever occupies the slug — a `running` job, an `unknown` one
|
|
140
|
+
* the robot has not accounted for yet, or an `external` goal someone else
|
|
141
|
+
* started — and its `state` and `origin` say which, so a caller can tell
|
|
142
|
+
* "wait for it" from "cancel what someone else started".
|
|
114
143
|
*/
|
|
115
144
|
export const busyDetails = z.object({
|
|
116
145
|
running: job,
|
|
@@ -210,16 +239,16 @@ export const jobRun = z.object({
|
|
|
210
239
|
description: 'Whether the slug was an `action` or a `service`.',
|
|
211
240
|
}),
|
|
212
241
|
state: jobState.meta({
|
|
213
|
-
description: 'How the run ended, or `running` while it is still going. `lost`
|
|
242
|
+
description: 'How the run ended, or `running` while it is still going. `unknown` while the robot has not accounted for it — offline or silent — and updated once the bridge says how it stands. `lost` is final: the bridge did not know the run and nothing else ran on its action, so the outcome is unknowable rather than unknown.',
|
|
214
243
|
}),
|
|
215
244
|
started_at: z.iso.datetime().meta({
|
|
216
245
|
description: 'When the run started, as an ISO 8601 timestamp. Runs are listed and filtered by this instant.',
|
|
217
246
|
}),
|
|
218
247
|
ended_at: z.iso.datetime().nullable().meta({
|
|
219
|
-
description: 'When the run finished, as an ISO 8601 timestamp. `null` while it is still `running` — a run has an end only once it has one.',
|
|
248
|
+
description: 'When the run finished, as an ISO 8601 timestamp. `null` while it is still `running` or `unknown` — a run has an end only once it has one.',
|
|
220
249
|
}),
|
|
221
250
|
duration_ms: z.number().int().nonnegative().nullable().meta({
|
|
222
|
-
description: 'How long the run took, in milliseconds. `null` while it is still `running`, never `0` standing in for "nothing so far".',
|
|
251
|
+
description: 'How long the run took, in milliseconds. `null` while it is still `running` or `unknown`, never `0` standing in for "nothing so far".',
|
|
223
252
|
}),
|
|
224
253
|
result: z.unknown().nullable().meta({
|
|
225
254
|
description: 'What the action or service returned once it succeeded, shaped by ROS itself. `null` otherwise.',
|
|
@@ -278,7 +307,7 @@ export const jobRunQuery = z
|
|
|
278
307
|
description: 'Only runs of this action or service.',
|
|
279
308
|
}),
|
|
280
309
|
state: jobState.optional().meta({
|
|
281
|
-
description: 'Only runs in this state — `running`, `succeeded`, `failed`, `cancelled` or `lost`.',
|
|
310
|
+
description: 'Only runs in this state — `running`, `unknown`, `succeeded`, `failed`, `cancelled` or `lost`.',
|
|
282
311
|
}),
|
|
283
312
|
kind: jobRunKind.optional().meta({
|
|
284
313
|
description: 'Only `action` runs, or only `service` runs.',
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
/**
|
|
4
|
-
* Bridge <-> cloud protocol, version
|
|
4
|
+
* Bridge <-> cloud protocol, version 5.
|
|
5
5
|
*
|
|
6
6
|
* The version is exchanged in the hello handshake. Since 2026-09 the cloud
|
|
7
7
|
* serves a **window** of versions, not one: every entry of
|
|
8
8
|
* `PROTOCOL_VERSIONS` whose sunset has not passed. A version is deprecated
|
|
9
9
|
* by the cloud release that supersedes it and sunset `PROTOCOL_SUNSET_DAYS`
|
|
10
|
-
* later. Outside the window the cloud refuses
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* later. Outside the window the cloud refuses the hello, and the refusal
|
|
11
|
+
* reaches the robot's detail view as `last_hello_error`: `bridge_too_old`,
|
|
12
|
+
* naming the bridge version to install, for a version below the window.
|
|
13
13
|
*
|
|
14
|
-
* **
|
|
14
|
+
* **5 (2026-09-29):** a hard cut, not a window — protocols 2, 3 and 4 are
|
|
15
|
+
* unsupported from this release on, with no sunset (André, 2026-09-29: "we
|
|
16
|
+
* are still building up and need not take care"), so `PROTOCOL_VERSIONS`
|
|
17
|
+
* holds one entry. The bridge tracks every goal on a published action by
|
|
18
|
+
* goal id — its own and anyone else's — and reports them through
|
|
19
|
+
* `job_update`, which gains a required `origin` and `goal_id`: a goal it did
|
|
20
|
+
* not send arrives as `origin: 'external'` under a job id the bridge derives
|
|
21
|
+
* itself. `jobState` gains `unknown`, the cloud's non-terminal "I lost sight
|
|
22
|
+
* of it" (`bridge_disconnected`, `bridge_timeout`) that replaces settling
|
|
23
|
+
* `lost` on a guess; `lost` is final. While connected, the cloud asks about
|
|
24
|
+
* specific jobs with `job_query` and the bridge answers `job_status`. Goal
|
|
25
|
+
* state is reported once per `JOB_HEARTBEAT_INTERVAL_MS`, newest only; the
|
|
26
|
+
* end of a Fleetless job goes at once.
|
|
27
|
+
*
|
|
28
|
+
* **4 (2026-09-29):** the bridge sends a `job_update` heartbeat at
|
|
15
29
|
* `JOB_HEARTBEAT_INTERVAL_MS` for every running job, whether or not the
|
|
16
|
-
* action said anything new, and
|
|
17
|
-
* `
|
|
30
|
+
* action said anything new, and ends a job whose action server vanished
|
|
31
|
+
* `lost` with `action_server_lost` (a terminal `job_update`; `job_lost`
|
|
32
|
+
* gains an optional `error` for the same purpose). The cloud bounds
|
|
18
33
|
* a protocol-4 job's silence by the heartbeat (`JOB_HEARTBEAT_TIMEOUT_MS`)
|
|
19
34
|
* once it has heard from the job at all; `patience_ms` still bounds
|
|
20
35
|
* acceptance, the same as before. Offline tolerance is
|
|
@@ -35,7 +50,7 @@ import { z } from 'zod';
|
|
|
35
50
|
* **2 (2026-08-21):** `config_applied.errors` entries gained `kind` and `code`
|
|
36
51
|
* beside `message`.
|
|
37
52
|
*/
|
|
38
|
-
export declare const PROTOCOL_VERSION =
|
|
53
|
+
export declare const PROTOCOL_VERSION = 5;
|
|
39
54
|
/** Days between a version's deprecation and its sunset. */
|
|
40
55
|
export declare const PROTOCOL_SUNSET_DAYS = 90;
|
|
41
56
|
export interface ProtocolVersionEntry {
|
|
@@ -46,17 +61,24 @@ export interface ProtocolVersionEntry {
|
|
|
46
61
|
deprecated_at: string | null;
|
|
47
62
|
}
|
|
48
63
|
/**
|
|
49
|
-
* Every protocol version the cloud
|
|
64
|
+
* Every protocol version the cloud serves, oldest first. A test keeps
|
|
50
65
|
* exactly one entry current and equal to `PROTOCOL_VERSION`; `test/changelog.test.ts`
|
|
51
66
|
* requires some CHANGELOG section — `[Unreleased]` or a dated one — to name
|
|
52
67
|
* the newest `bridge_from` together with the previous entry's `sunsetOf(...)`
|
|
53
68
|
* date, so the pull request that moves this window is the one that fails
|
|
54
69
|
* without saying so; and `scripts/verify-version-tag.mjs` requires a dated
|
|
55
70
|
* heading for the tag being released.
|
|
71
|
+
*
|
|
72
|
+
* **One entry since protocol 5**, which cut 2, 3 and 4 without a sunset
|
|
73
|
+
* rather than deprecating them. A version absent from the table is
|
|
74
|
+
* `unsupported`, which is exactly what a cut means, so the dropped entries
|
|
75
|
+
* are gone rather than kept with a past date. The changelog test's window
|
|
76
|
+
* check has no previous entry to read then; its hard-cut sibling holds the
|
|
77
|
+
* changelog to naming the cut instead.
|
|
56
78
|
*/
|
|
57
79
|
export declare const PROTOCOL_VERSIONS: readonly ProtocolVersionEntry[];
|
|
58
80
|
/** The newest bridge package. The cloud mails organisations still below it. */
|
|
59
|
-
export declare const LATEST_BRIDGE_VERSION = "
|
|
81
|
+
export declare const LATEST_BRIDGE_VERSION = "6.0.0";
|
|
60
82
|
export interface ProtocolStatus {
|
|
61
83
|
status: 'current' | 'deprecated' | 'unsupported';
|
|
62
84
|
/** ISO date, or null for a current or unknown version. */
|
|
@@ -143,37 +165,43 @@ export declare const MAX_PATIENCE_MS = 120000;
|
|
|
143
165
|
*/
|
|
144
166
|
export declare const MIN_PATIENCE_MS = 1000;
|
|
145
167
|
/**
|
|
146
|
-
* How often
|
|
147
|
-
*
|
|
148
|
-
*
|
|
168
|
+
* How often the bridge reports goal state: one `job_update` per active goal
|
|
169
|
+
* on a published action — its own and external ones — carrying the newest
|
|
170
|
+
* state and feedback, whether or not the action said anything new. Whatever
|
|
171
|
+
* happened in between is dropped, so an action that sends feedback at 100 Hz
|
|
172
|
+
* costs one frame a second; the end of a Fleetless job is the exception and
|
|
173
|
+
* goes at once. One second: often enough that `JOB_HEARTBEAT_TIMEOUT_MS`
|
|
149
174
|
* can be a small multiple of it and still absorb a missed beat or two, rare
|
|
150
175
|
* enough that it costs nothing next to the datapoint traffic a busy robot
|
|
151
176
|
* already sends.
|
|
152
177
|
*/
|
|
153
178
|
export declare const JOB_HEARTBEAT_INTERVAL_MS = 1000;
|
|
154
179
|
/**
|
|
155
|
-
* How long a
|
|
156
|
-
*
|
|
180
|
+
* How long a running job may go without a `job_update` — heartbeat or real
|
|
181
|
+
* progress, either counts — before the cloud marks it `unknown` with
|
|
157
182
|
* `bridge_timeout`, once the bridge is connected. Five heartbeats: enough
|
|
158
183
|
* slack for an ordinary scheduling jitter, small next to `patience_ms`
|
|
159
|
-
* because it
|
|
160
|
-
* bounds only the time from `invoke` to the *first* update
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* `
|
|
164
|
-
*
|
|
184
|
+
* because it does not have to cover the acceptance gap too. `patience_ms`
|
|
185
|
+
* bounds only the time from `invoke` to the *first* update; every rearm after
|
|
186
|
+
* that uses this constant instead.
|
|
187
|
+
*
|
|
188
|
+
* `unknown`, not `lost`: silence is the cloud's guess, not the bridge's
|
|
189
|
+
* statement. The cloud then asks with `job_query`, and asks again after the
|
|
190
|
+
* same interval for as long as no `job_status` answers and the bridge stays
|
|
191
|
+
* connected.
|
|
165
192
|
*/
|
|
166
193
|
export declare const JOB_HEARTBEAT_TIMEOUT_MS = 5000;
|
|
167
194
|
/**
|
|
168
195
|
* How long a running job survives its robot going offline before the cloud
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* result the cloud is waiting for is often still coming.
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
196
|
+
* marks it `unknown` with `bridge_disconnected`. Five minutes: long enough
|
|
197
|
+
* that an ordinary Wi-Fi dead zone — the case this constant exists for —
|
|
198
|
+
* never touches a job, since a robot with no safety layer of its own
|
|
199
|
+
* (§ Fleetless is not a safety layer) keeps driving through one and the
|
|
200
|
+
* result the cloud is waiting for is often still coming. Past it the job is
|
|
201
|
+
* still not given up: `unknown` keeps the slug occupied until the
|
|
202
|
+
* reconnecting bridge says how the job stands. A robot connected the whole
|
|
203
|
+
* time never reaches this bound at all: while online, silence is
|
|
204
|
+
* `JOB_HEARTBEAT_TIMEOUT_MS`'s question, never this one's.
|
|
177
205
|
*/
|
|
178
206
|
export declare const JOB_OFFLINE_GRACE_MS = 300000;
|
|
179
207
|
/** Re-exported so consumers keep importing wire names from one place. */
|
|
@@ -192,11 +220,14 @@ export { slug } from './common.js';
|
|
|
192
220
|
* `state` is the bridge's own current answer, not a history. A bridge that
|
|
193
221
|
* has a terminal result still in hand reports it here and the cloud writes it
|
|
194
222
|
* down, instead of publishing `lost` over a job that in fact succeeded.
|
|
223
|
+
* Never `unknown`: that is the cloud's word for not having heard, and a
|
|
224
|
+
* bridge listing a job has, by definition, something to say about it.
|
|
195
225
|
*/
|
|
196
226
|
export declare const activeJob: z.ZodObject<{
|
|
197
227
|
job_id: z.ZodUUID;
|
|
198
228
|
slug: z.ZodString;
|
|
199
229
|
state: z.ZodEnum<{
|
|
230
|
+
unknown: "unknown";
|
|
200
231
|
failed: "failed";
|
|
201
232
|
running: "running";
|
|
202
233
|
succeeded: "succeeded";
|
|
@@ -215,6 +246,7 @@ export declare const bridgeHello: z.ZodObject<{
|
|
|
215
246
|
job_id: z.ZodUUID;
|
|
216
247
|
slug: z.ZodString;
|
|
217
248
|
state: z.ZodEnum<{
|
|
249
|
+
unknown: "unknown";
|
|
218
250
|
failed: "failed";
|
|
219
251
|
running: "running";
|
|
220
252
|
succeeded: "succeeded";
|
|
@@ -614,18 +646,38 @@ export type CloudPublish = z.infer<typeof cloudPublish>;
|
|
|
614
646
|
/**
|
|
615
647
|
* Progress on a job, bridge → cloud. `timestamp_ms` is capture time, so a
|
|
616
648
|
* burst delivered late after a reconnect is visibly late.
|
|
649
|
+
*
|
|
650
|
+
* Since protocol 5 this frame reports **every goal active on a published
|
|
651
|
+
* action**, not only the ones the bridge sent. A goal the bridge cannot
|
|
652
|
+
* attribute to a job of its own arrives with `origin: 'external'` and a
|
|
653
|
+
* `job_id` the bridge derives from robot, slug and goal id, the same id on
|
|
654
|
+
* every report of that goal — the cloud mints an external job the first time
|
|
655
|
+
* it sees one and never recomputes the id itself. `goal_id` is the ROS 2
|
|
656
|
+
* goal id, so a cancel can name the goal; `null` for a service job, which
|
|
657
|
+
* has no goal.
|
|
658
|
+
*
|
|
659
|
+
* Sent once per `JOB_HEARTBEAT_INTERVAL_MS` per active goal, newest state
|
|
660
|
+
* only. The end of a Fleetless job is sent at once; an external goal's end at
|
|
661
|
+
* the next tick, and an external goal that started and ended between two
|
|
662
|
+
* ticks is never reported at all.
|
|
617
663
|
*/
|
|
618
664
|
export declare const bridgeJobUpdate: z.ZodObject<{
|
|
619
665
|
type: z.ZodLiteral<"job_update">;
|
|
620
666
|
job_id: z.ZodUUID;
|
|
621
667
|
slug: z.ZodString;
|
|
622
668
|
state: z.ZodEnum<{
|
|
669
|
+
unknown: "unknown";
|
|
623
670
|
failed: "failed";
|
|
624
671
|
running: "running";
|
|
625
672
|
succeeded: "succeeded";
|
|
626
673
|
cancelled: "cancelled";
|
|
627
674
|
lost: "lost";
|
|
628
675
|
}>;
|
|
676
|
+
origin: z.ZodEnum<{
|
|
677
|
+
fleetless: "fleetless";
|
|
678
|
+
external: "external";
|
|
679
|
+
}>;
|
|
680
|
+
goal_id: z.ZodNullable<z.ZodString>;
|
|
629
681
|
feedback: z.ZodNullable<z.ZodUnknown>;
|
|
630
682
|
progress: z.ZodNullable<z.ZodNumber>;
|
|
631
683
|
result: z.ZodNullable<z.ZodUnknown>;
|
|
@@ -638,23 +690,20 @@ export declare const bridgeJobUpdate: z.ZodObject<{
|
|
|
638
690
|
}, z.core.$strip>;
|
|
639
691
|
export type BridgeJobUpdate = z.infer<typeof bridgeJobUpdate>;
|
|
640
692
|
/**
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
* the honest answer is "I lost this" rather
|
|
693
|
+
* The bridge's own, definite statement **while connected** that it no longer
|
|
694
|
+
* knows these jobs — an action server that vanished mid-goal, a goal the
|
|
695
|
+
* server no longer knows — where the honest answer is "I lost this" rather
|
|
696
|
+
* than a state. The cloud settles each named job `lost`, final.
|
|
644
697
|
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
* leaving a job reading "running" because nobody contradicted it.
|
|
698
|
+
* Only the bridge says `lost` now. The cloud's own guesses — offline past
|
|
699
|
+
* `JOB_OFFLINE_GRACE_MS`, silent past `JOB_HEARTBEAT_TIMEOUT_MS` — make a
|
|
700
|
+
* job `unknown` instead, and a restart is answered by `hello.active_jobs`.
|
|
649
701
|
*
|
|
650
|
-
* **`error`
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
* the cloud settles the job `lost` with no specific code, the same as a
|
|
656
|
-
* protocol-3 bridge's frame, which carries no `error` at all and still
|
|
657
|
-
* parses under this schema unchanged.
|
|
702
|
+
* **`error` is optional and, when present, applies to every job named in
|
|
703
|
+
* `job_ids`.** A vanished action server is discovered once, by the bridge's
|
|
704
|
+
* own liveness check on that one action, so a frame naming several jobs at
|
|
705
|
+
* once always shares the same cause. Absent, the cloud settles the job
|
|
706
|
+
* `lost` with no specific code.
|
|
658
707
|
*/
|
|
659
708
|
export declare const bridgeJobLost: z.ZodObject<{
|
|
660
709
|
type: z.ZodLiteral<"job_lost">;
|
|
@@ -665,6 +714,78 @@ export declare const bridgeJobLost: z.ZodObject<{
|
|
|
665
714
|
}, z.core.$strip>>;
|
|
666
715
|
}, z.core.$strip>;
|
|
667
716
|
export type BridgeJobLost = z.infer<typeof bridgeJobLost>;
|
|
717
|
+
/**
|
|
718
|
+
* The cloud asks the bridge how specific jobs stand, while connected;
|
|
719
|
+
* `request_id` correlates the `job_status` answer.
|
|
720
|
+
*
|
|
721
|
+
* Sent for a job that went `unknown` with `bridge_timeout`: the bridge is
|
|
722
|
+
* connected but the cloud has not heard about the job, so it asks instead of
|
|
723
|
+
* guessing. Unanswered within `JOB_HEARTBEAT_TIMEOUT_MS`, the job stays
|
|
724
|
+
* `unknown` and the cloud asks again after the same interval.
|
|
725
|
+
*/
|
|
726
|
+
export declare const cloudJobQuery: z.ZodObject<{
|
|
727
|
+
type: z.ZodLiteral<"job_query">;
|
|
728
|
+
request_id: z.ZodString;
|
|
729
|
+
job_ids: z.ZodArray<z.ZodUUID>;
|
|
730
|
+
}, z.core.$strip>;
|
|
731
|
+
export type CloudJobQuery = z.infer<typeof cloudJobQuery>;
|
|
732
|
+
/** One job the bridge recognises, in a `job_status` answer. Same fields as `job_update`'s. */
|
|
733
|
+
export declare const bridgeJobStatusEntry: z.ZodObject<{
|
|
734
|
+
job_id: z.ZodUUID;
|
|
735
|
+
state: z.ZodEnum<{
|
|
736
|
+
unknown: "unknown";
|
|
737
|
+
failed: "failed";
|
|
738
|
+
running: "running";
|
|
739
|
+
succeeded: "succeeded";
|
|
740
|
+
cancelled: "cancelled";
|
|
741
|
+
lost: "lost";
|
|
742
|
+
}>;
|
|
743
|
+
feedback: z.ZodNullable<z.ZodUnknown>;
|
|
744
|
+
progress: z.ZodNullable<z.ZodNumber>;
|
|
745
|
+
result: z.ZodNullable<z.ZodUnknown>;
|
|
746
|
+
error: z.ZodNullable<z.ZodObject<{
|
|
747
|
+
code: z.ZodString;
|
|
748
|
+
message: z.ZodString;
|
|
749
|
+
details: z.ZodOptional<z.ZodUnknown>;
|
|
750
|
+
}, z.core.$strip>>;
|
|
751
|
+
}, z.core.$strip>;
|
|
752
|
+
export type BridgeJobStatusEntry = z.infer<typeof bridgeJobStatusEntry>;
|
|
753
|
+
/**
|
|
754
|
+
* The bridge's answer to a `job_query`. Every queried id lands in exactly
|
|
755
|
+
* one of the two lists.
|
|
756
|
+
*
|
|
757
|
+
* `unknown_job_ids` is an answer, not a failure — the same stance
|
|
758
|
+
* `type_definitions.unresolved` takes. It names the queried jobs the bridge
|
|
759
|
+
* does not recognise at all; the cloud settles one `lost` with
|
|
760
|
+
* `job_unknown_to_bridge` once no goal the bridge cannot attribute is active
|
|
761
|
+
* on its action (one of those may be that very job), and at once for a
|
|
762
|
+
* service job.
|
|
763
|
+
*/
|
|
764
|
+
export declare const bridgeJobStatus: z.ZodObject<{
|
|
765
|
+
type: z.ZodLiteral<"job_status">;
|
|
766
|
+
request_id: z.ZodString;
|
|
767
|
+
jobs: z.ZodArray<z.ZodObject<{
|
|
768
|
+
job_id: z.ZodUUID;
|
|
769
|
+
state: z.ZodEnum<{
|
|
770
|
+
unknown: "unknown";
|
|
771
|
+
failed: "failed";
|
|
772
|
+
running: "running";
|
|
773
|
+
succeeded: "succeeded";
|
|
774
|
+
cancelled: "cancelled";
|
|
775
|
+
lost: "lost";
|
|
776
|
+
}>;
|
|
777
|
+
feedback: z.ZodNullable<z.ZodUnknown>;
|
|
778
|
+
progress: z.ZodNullable<z.ZodNumber>;
|
|
779
|
+
result: z.ZodNullable<z.ZodUnknown>;
|
|
780
|
+
error: z.ZodNullable<z.ZodObject<{
|
|
781
|
+
code: z.ZodString;
|
|
782
|
+
message: z.ZodString;
|
|
783
|
+
details: z.ZodOptional<z.ZodUnknown>;
|
|
784
|
+
}, z.core.$strip>>;
|
|
785
|
+
}, z.core.$strip>>;
|
|
786
|
+
unknown_job_ids: z.ZodArray<z.ZodUUID>;
|
|
787
|
+
}, z.core.$strip>;
|
|
788
|
+
export type BridgeJobStatus = z.infer<typeof bridgeJobStatus>;
|
|
668
789
|
/** Cloud asks for a fresh ROS graph; `request_id` correlates the answer. */
|
|
669
790
|
export declare const cloudIntrospectRequest: z.ZodObject<{
|
|
670
791
|
type: z.ZodLiteral<"introspect_request">;
|