@fleetless/contracts 4.0.0 → 5.0.0-next.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 (36) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/artifacts/constants.json +4 -14
  3. package/artifacts/openapi.json +46 -13
  4. package/artifacts/routes.json +2 -2
  5. package/artifacts/schema/bridge-cancel-result.schema.json +86 -0
  6. package/artifacts/schema/bridge-job-status.schema.json +113 -0
  7. package/artifacts/schema/bridge-job-update.schema.json +20 -0
  8. package/artifacts/schema/busy-details.schema.json +12 -2
  9. package/artifacts/schema/cloud-cancel.schema.json +6 -0
  10. package/artifacts/schema/cloud-job-query.schema.json +29 -0
  11. package/artifacts/schema/command-result.schema.json +12 -2
  12. package/artifacts/schema/invoke-or-service-response.schema.json +12 -2
  13. package/artifacts/schema/invoke-response.schema.json +12 -2
  14. package/artifacts/schema/job-event.schema.json +12 -2
  15. package/artifacts/schema/job-response.schema.json +12 -2
  16. package/artifacts/schema/job-run-list-response.schema.json +4 -3
  17. package/artifacts/schema/job-run-query.schema.json +2 -1
  18. package/artifacts/schema/job-run.schema.json +4 -3
  19. package/artifacts/schema/job-state.schema.json +1 -0
  20. package/artifacts/schema/job.schema.json +12 -2
  21. package/artifacts/schema/robot-jobs-response.schema.json +12 -2
  22. package/artifacts/schema-outgoing/bridge-cancel-result.schema.json +89 -0
  23. package/artifacts/schema-outgoing/bridge-job-status.schema.json +116 -0
  24. package/artifacts/schema-outgoing/bridge-job-update.schema.json +20 -0
  25. package/dist/errors.d.ts +1 -1
  26. package/dist/errors.js +46 -7
  27. package/dist/index.d.ts +4 -4
  28. package/dist/index.js +2 -2
  29. package/dist/jobs.d.ts +70 -6
  30. package/dist/jobs.js +51 -14
  31. package/dist/protocol.d.ts +233 -46
  32. package/dist/protocol.js +207 -62
  33. package/dist/realtime.d.ts +5 -0
  34. package/dist/rest.d.ts +20 -0
  35. package/dist/routes.js +6 -3
  36. package/package.json +1 -1
package/dist/errors.js CHANGED
@@ -51,6 +51,16 @@ export const ERROR_CODES = [
51
51
  'unknown_datapoint',
52
52
  'invalid_token',
53
53
  'protocol_mismatch',
54
+ /**
55
+ * The cloud's `hello_error` for a bridge whose `protocol_version` is below
56
+ * every version it serves — since protocol 5, anything below 5. Its message
57
+ * names the bridge release to install (`LATEST_BRIDGE_VERSION`, 6.0.0 at
58
+ * the cut), because "too old" alone leaves an operator guessing how far to
59
+ * upgrade. Distinct from `protocol_mismatch`, the generic refusal for a
60
+ * version the cloud cannot place: this one says which way the gap runs and
61
+ * what closes it. Reaches the robot's detail view as `last_hello_error`.
62
+ */
63
+ 'bridge_too_old',
54
64
  'invalid_frame',
55
65
  // Configuration.
56
66
  'duplicate_slug',
@@ -74,9 +84,10 @@ export const ERROR_CODES = [
74
84
  /**
75
85
  * The cloud has heard nothing — heartbeat or real progress — from a
76
86
  * running job for longer than it tolerates while the bridge is connected:
77
- * `patience_ms` for a protocol-3 bridge, `JOB_HEARTBEAT_TIMEOUT_MS` for a
78
- * protocol-4 one once it has heard from the job at all. `job.error.code`
79
- * on `lost`.
87
+ * `patience_ms` until the first report, `JOB_HEARTBEAT_TIMEOUT_MS` after
88
+ * it. `job.error.code` on `unknown`, not `lost`: silence is the cloud's
89
+ * guess, so it asks the bridge with `job_query` and the answer resolves the
90
+ * job — running again clears this code, an end replaces it.
80
91
  */
81
92
  'bridge_timeout',
82
93
  // Identity and rights. `forbidden` is deliberately the answer both
@@ -145,8 +156,34 @@ export const ERROR_CODES = [
145
156
  'busy',
146
157
  /** A parameter failed its declared rule; details name the field and the rule. */
147
158
  'parameter_invalid',
148
- /** The bridge could not account for this job after a restart. */
159
+ /**
160
+ * The action server refused a cancel request — `CancelGoal` answered
161
+ * `ERROR_REJECTED` (the bridge's `cancel_result`). The caller's cancel is
162
+ * refused with this code, never reported as success; `details.goals` carry
163
+ * each goal's `job_id`, `goal_id` and `return_code`. Whether the goal ends
164
+ * anyway is what its `job_update` says afterwards.
165
+ */
166
+ 'cancel_rejected',
167
+ /**
168
+ * The bridge's own statement, while connected, that it lost track of a job
169
+ * it still names — the vocabulary behind its `job_lost` frame. Distinct
170
+ * from `job_unknown_to_bridge`, the cloud's conclusion about a job the
171
+ * bridge does not name at all.
172
+ */
149
173
  'job_lost',
174
+ /**
175
+ * The bridge does not know this job — its `hello.active_jobs` or a
176
+ * `job_status` answer leaves it out — and, for an action, no goal the
177
+ * bridge cannot attribute is active on the job's action any more, so none
178
+ * of them can be it. A `job.error.code` on `lost`, final: how an `unknown`
179
+ * job the bridge has no word about ends. A service job, which has no goals to look
180
+ * at, gets it as soon as the bridge does not know it; so does a job whose
181
+ * persisted goal the action server no longer knows (its result expired).
182
+ * Distinct from `job_lost`, the bridge's own statement about a job it
183
+ * still names, and from `bridge_disconnected`/`bridge_timeout`, the
184
+ * cloud's guesses that make a job `unknown` in the first place.
185
+ */
186
+ 'job_unknown_to_bridge',
150
187
  /**
151
188
  * The robot's action server vanished mid-goal — the bridge's own liveness
152
189
  * check found `server_is_ready()` false for three seconds straight and
@@ -167,9 +204,11 @@ export const ERROR_CODES = [
167
204
  'goal_uncontrollable',
168
205
  /**
169
206
  * The robot stayed offline for longer than `JOB_OFFLINE_GRACE_MS` while a
170
- * job was running. A late real outcome, if the robot reconnects and the
171
- * bridge still has it, corrects this — it is not final the way a genuine
172
- * bridge report is. `job.error.code` on `lost`.
207
+ * job was running. `job.error.code` on `unknown`, not `lost`: the cloud
208
+ * does not know how the job stands, and the reconnecting bridge's
209
+ * `hello.active_jobs` resolves it — running again clears this code, an end
210
+ * replaces it, and a job the bridge does not know becomes `lost` with
211
+ * `job_unknown_to_bridge`.
173
212
  */
174
213
  'bridge_disconnected',
175
214
  /**
package/dist/index.d.ts CHANGED
@@ -3,10 +3,10 @@ export { SLUG_RULE, ROS_NAME_RULE, ROS_TYPE_NAME_RULE, FIELD_PATH_RULE, slug, ro
3
3
  export type { ApplyErrorKind, ApplyError } from './common.js';
4
4
  export { MCP_PROTOCOL_VERSION, MCP_ENDPOINT_PATH, mcpAppEndpointPath, MCP_APP_PATHS, MCP_TOOL_NAME_MAX, MCP_ASSET_LINK_PATH, MCP_ASSET_LINK_TTL_MS, mcpToolNamePattern, mcpToolKind, mcpExposure, mcpCapabilities, mcpRobotDatasheet, mcpRolePreviewResponse, } from './mcp.js';
5
5
  export type { McpAppPaths, McpToolKind, McpExposure, McpCapabilities, McpRobotDatasheet, McpRolePreviewResponse, } from './mcp.js';
6
- export { PROTOCOL_VERSION, PROTOCOL_VERSIONS, PROTOCOL_SUNSET_DAYS, LATEST_BRIDGE_VERSION, protocolStatus, minimumProtocolVersion, sunsetOf, statusFromTable, bridgeHello, cloudHelloOk, cloudHelloError, cloudPing, bridgePong, bridgeLinkMode, datapointFrame, bridgeState, cloudConfig, bridgeConfigApplied, cloudIntrospectRequest, bridgeIntrospect, cloudTypeRequest, bridgeTypeDefinitions, cloudInvoke, cloudCancel, cloudPublish, bridgeJobUpdate, bridgeJobLost, snapshotHeader, cloudCameraStart, cloudCameraStop, bridgeCameraState, SNAPSHOT_MAX_BYTES, CLOSE_ROBOT_DELETED, CLOSE_TOKEN_ROTATED, bridgeAssetsAvailable, cloudAssetRequest, bridgeAssetProgress, activeJob, DEFAULT_PATIENCE_MS, MAX_PATIENCE_MS, MIN_PATIENCE_MS, JOB_HEARTBEAT_INTERVAL_MS, JOB_HEARTBEAT_TIMEOUT_MS, JOB_OFFLINE_GRACE_MS, } from './protocol.js';
7
- export type { ProtocolVersionEntry, ProtocolStatus, BridgeHello, CloudHelloOk, CloudHelloError, CloudPing, BridgePong, BridgeLinkMode, DatapointFrame, BridgeState, CloudConfig, BridgeConfigApplied, CloudIntrospectRequest, BridgeIntrospect, CloudTypeRequest, BridgeTypeDefinitions, CloudInvoke, CloudCancel, CloudPublish, BridgeJobUpdate, BridgeJobLost, SnapshotHeader, CloudCameraStart, CloudCameraStop, BridgeCameraState, ActiveJob, BridgeAssetsAvailable, CloudAssetRequest, BridgeAssetProgress, } from './protocol.js';
8
- export { jobState, job, jobEvent, busyDetails, publisherBusyDetails, jobQueueFullDetails } from './jobs.js';
9
- export type { JobState, Job, JobEvent, BusyDetails, PublisherBusyDetails, JobQueueFullDetails, } from './jobs.js';
6
+ export { PROTOCOL_VERSION, PROTOCOL_VERSIONS, PROTOCOL_SUNSET_DAYS, LATEST_BRIDGE_VERSION, protocolStatus, minimumProtocolVersion, sunsetOf, statusFromTable, bridgeHello, cloudHelloOk, cloudHelloError, cloudPing, bridgePong, bridgeLinkMode, datapointFrame, bridgeState, cloudConfig, bridgeConfigApplied, cloudIntrospectRequest, bridgeIntrospect, cloudTypeRequest, bridgeTypeDefinitions, cloudInvoke, cloudCancel, CANCEL_RETURN_CODES, cancelReturnCode, bridgeCancelResultEntry, bridgeCancelResult, cloudPublish, bridgeJobUpdate, bridgeJobLost, cloudJobQuery, bridgeJobStatusEntry, bridgeJobStatus, snapshotHeader, cloudCameraStart, cloudCameraStop, bridgeCameraState, SNAPSHOT_MAX_BYTES, CLOSE_ROBOT_DELETED, CLOSE_TOKEN_ROTATED, bridgeAssetsAvailable, cloudAssetRequest, bridgeAssetProgress, activeJob, DEFAULT_PATIENCE_MS, MAX_PATIENCE_MS, MIN_PATIENCE_MS, JOB_HEARTBEAT_INTERVAL_MS, JOB_HEARTBEAT_TIMEOUT_MS, JOB_OFFLINE_GRACE_MS, } from './protocol.js';
7
+ export type { ProtocolVersionEntry, ProtocolStatus, BridgeHello, CloudHelloOk, CloudHelloError, CloudPing, BridgePong, BridgeLinkMode, DatapointFrame, BridgeState, CloudConfig, BridgeConfigApplied, CloudIntrospectRequest, BridgeIntrospect, CloudTypeRequest, BridgeTypeDefinitions, CloudInvoke, CloudCancel, CancelReturnCode, BridgeCancelResultEntry, BridgeCancelResult, CloudPublish, BridgeJobUpdate, BridgeJobLost, CloudJobQuery, BridgeJobStatusEntry, BridgeJobStatus, SnapshotHeader, CloudCameraStart, CloudCameraStop, BridgeCameraState, ActiveJob, BridgeAssetsAvailable, CloudAssetRequest, BridgeAssetProgress, } from './protocol.js';
8
+ export { jobState, reportedJobState, jobOrigin, job, jobEvent, busyDetails, publisherBusyDetails, jobQueueFullDetails } from './jobs.js';
9
+ export type { JobState, ReportedJobState, JobOrigin, Job, JobEvent, BusyDetails, PublisherBusyDetails, JobQueueFullDetails, } from './jobs.js';
10
10
  export { JOB_RUN_PAGE_MAX, JOB_RUN_RETENTION_DAYS, jobActor, jobRunKind, jobRun, jobRunQuery, jobRunListResponse, jobRunSummaryQuery, jobRunSummary, } from './jobs.js';
11
11
  export type { JobActor, JobRunKind, JobRun, JobRunQuery, JobRunListResponse, JobRunSummary } from './jobs.js';
12
12
  export { FLEETLESS_FORMAT_VERSION, RESERVED_SLUGS, parameterType, parameterSpec, parameterMap, serviceDescription, parameterDescription, messageTemplate, messageRef, messageBody, messageMap, PLACEHOLDER_RE, placeholderNames, actionConfig, serviceConfig, publisherConfig, cameraConfig, alertCondition, datapointAlert, rateThrottleHz, datapointNumeric, datapointRetention, datapointChart, datapointConfig, lowBandwidthSection, LOW_BANDWIDTH_DEFAULTS, robotConfigDoc, validationIssue, configState, snapshotIntervalSeconds, ALERT_SEVERITY_DEFAULT, ALERT_ENABLED_DEFAULT, RETENTION_INTERVAL_SECONDS_DEFAULT, CHART_WINDOW_MINUTES_DEFAULT, cameraSource, cameraCredentials, } from './config.js';
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  export { SLUG_RULE, ROS_NAME_RULE, ROS_TYPE_NAME_RULE, FIELD_PATH_RULE, slug, rosName, rosTypeName, fieldPath, wireSeqCursor, wireTimestampMs, applyErrorKind, applyError, } from './common.js';
3
3
  export { MCP_PROTOCOL_VERSION, MCP_ENDPOINT_PATH, mcpAppEndpointPath, MCP_APP_PATHS, MCP_TOOL_NAME_MAX, MCP_ASSET_LINK_PATH, MCP_ASSET_LINK_TTL_MS, mcpToolNamePattern, mcpToolKind, mcpExposure, mcpCapabilities, mcpRobotDatasheet, mcpRolePreviewResponse, } from './mcp.js';
4
- export { PROTOCOL_VERSION, PROTOCOL_VERSIONS, PROTOCOL_SUNSET_DAYS, LATEST_BRIDGE_VERSION, protocolStatus, minimumProtocolVersion, sunsetOf, statusFromTable, bridgeHello, cloudHelloOk, cloudHelloError, cloudPing, bridgePong, bridgeLinkMode, datapointFrame, bridgeState, cloudConfig, bridgeConfigApplied, cloudIntrospectRequest, bridgeIntrospect, cloudTypeRequest, bridgeTypeDefinitions, cloudInvoke, cloudCancel, cloudPublish, bridgeJobUpdate, bridgeJobLost, snapshotHeader, cloudCameraStart, cloudCameraStop, bridgeCameraState, SNAPSHOT_MAX_BYTES, CLOSE_ROBOT_DELETED, CLOSE_TOKEN_ROTATED,
4
+ export { PROTOCOL_VERSION, PROTOCOL_VERSIONS, PROTOCOL_SUNSET_DAYS, LATEST_BRIDGE_VERSION, protocolStatus, minimumProtocolVersion, sunsetOf, statusFromTable, bridgeHello, cloudHelloOk, cloudHelloError, cloudPing, bridgePong, bridgeLinkMode, datapointFrame, bridgeState, cloudConfig, bridgeConfigApplied, cloudIntrospectRequest, bridgeIntrospect, cloudTypeRequest, bridgeTypeDefinitions, cloudInvoke, cloudCancel, CANCEL_RETURN_CODES, cancelReturnCode, bridgeCancelResultEntry, bridgeCancelResult, cloudPublish, bridgeJobUpdate, bridgeJobLost, cloudJobQuery, bridgeJobStatusEntry, bridgeJobStatus, snapshotHeader, cloudCameraStart, cloudCameraStop, bridgeCameraState, SNAPSHOT_MAX_BYTES, CLOSE_ROBOT_DELETED, CLOSE_TOKEN_ROTATED,
5
5
  // Assets.
6
6
  bridgeAssetsAvailable, cloudAssetRequest, bridgeAssetProgress,
7
7
  // Addressing.
8
8
  activeJob, DEFAULT_PATIENCE_MS, MAX_PATIENCE_MS, MIN_PATIENCE_MS, JOB_HEARTBEAT_INTERVAL_MS, JOB_HEARTBEAT_TIMEOUT_MS, JOB_OFFLINE_GRACE_MS, } from './protocol.js';
9
- export { jobState, job, jobEvent, busyDetails, publisherBusyDetails, jobQueueFullDetails } from './jobs.js';
9
+ export { jobState, reportedJobState, jobOrigin, job, jobEvent, busyDetails, publisherBusyDetails, jobQueueFullDetails } from './jobs.js';
10
10
  export { JOB_RUN_PAGE_MAX, JOB_RUN_RETENTION_DAYS, jobActor, jobRunKind, jobRun, jobRunQuery, jobRunListResponse, jobRunSummaryQuery, jobRunSummary, } from './jobs.js';
11
11
  export { FLEETLESS_FORMAT_VERSION, RESERVED_SLUGS, parameterType, parameterSpec, parameterMap, serviceDescription, parameterDescription, messageTemplate, messageRef, messageBody, messageMap, PLACEHOLDER_RE, placeholderNames, actionConfig, serviceConfig, publisherConfig, cameraConfig, alertCondition, datapointAlert, rateThrottleHz, datapointNumeric, datapointRetention, datapointChart, datapointConfig, lowBandwidthSection, LOW_BANDWIDTH_DEFAULTS, robotConfigDoc, validationIssue, configState, snapshotIntervalSeconds,
12
12
  // The defaults the format names, so nobody invents them twice.
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. **`lost` is a real outcome and must be said out loud.** Job state
14
- * lives only in the bridge's memory; if it restarts mid-job, the results
15
- * are gone. The cloud then marks the job `lost` — never leaves it reading
16
- * "running" because nobody contradicted it. A system that reports a
17
- * machine is still working when it does not know is worse than one that
18
- * admits it lost track.
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,57 @@ export declare const jobState: z.ZodEnum<{
25
31
  lost: "lost";
26
32
  }>;
27
33
  export type JobState = z.infer<typeof jobState>;
34
+ /**
35
+ * The states a bridge may state about a job: every `jobState` but `unknown`,
36
+ * which is the cloud's own word for not having heard. The bridge's
37
+ * `job_update`, `job_status` entries and `hello.active_jobs` use this, so a
38
+ * bridge claiming `unknown` fails validation instead of parking a job
39
+ * nobody will ever ask about.
40
+ */
41
+ export declare const reportedJobState: z.ZodEnum<{
42
+ failed: "failed";
43
+ running: "running";
44
+ succeeded: "succeeded";
45
+ cancelled: "cancelled";
46
+ lost: "lost";
47
+ }>;
48
+ export type ReportedJobState = z.infer<typeof reportedJobState>;
49
+ /**
50
+ * Who started a job.
51
+ *
52
+ * `fleetless` for every job the cloud minted from an invocation. `external`
53
+ * for a goal the bridge found active on a published action without having
54
+ * sent it — started by anyone else on the robot's ROS graph, or the bridge's
55
+ * own goal after its mapping was lost. An external job has the same shape,
56
+ * states, live stream and cancel as any other, but no parameters (ROS 2
57
+ * publishes a goal's request nowhere), no starter, and it lives in memory
58
+ * only: it is never written to `job_runs` and never counts towards quotas.
59
+ */
60
+ export declare const jobOrigin: z.ZodEnum<{
61
+ fleetless: "fleetless";
62
+ external: "external";
63
+ }>;
64
+ export type JobOrigin = z.infer<typeof jobOrigin>;
65
+ /**
66
+ * One job, as the cloud tells every client about it — a Fleetless job or an
67
+ * external goal alike, told apart only by `origin`.
68
+ */
28
69
  export declare const job: z.ZodObject<{
29
70
  id: z.ZodUUID;
30
71
  robot_id: z.ZodUUID;
31
72
  slug: z.ZodString;
32
73
  state: z.ZodEnum<{
74
+ unknown: "unknown";
33
75
  failed: "failed";
34
76
  running: "running";
35
77
  succeeded: "succeeded";
36
78
  cancelled: "cancelled";
37
79
  lost: "lost";
38
80
  }>;
81
+ origin: z.ZodEnum<{
82
+ fleetless: "fleetless";
83
+ external: "external";
84
+ }>;
39
85
  started_at: z.ZodISODateTime;
40
86
  updated_at: z.ZodISODateTime;
41
87
  seq: z.ZodNumber;
@@ -65,12 +111,17 @@ export declare const jobEvent: z.ZodObject<{
65
111
  robot_id: z.ZodUUID;
66
112
  slug: z.ZodString;
67
113
  state: z.ZodEnum<{
114
+ unknown: "unknown";
68
115
  failed: "failed";
69
116
  running: "running";
70
117
  succeeded: "succeeded";
71
118
  cancelled: "cancelled";
72
119
  lost: "lost";
73
120
  }>;
121
+ origin: z.ZodEnum<{
122
+ fleetless: "fleetless";
123
+ external: "external";
124
+ }>;
74
125
  started_at: z.ZodISODateTime;
75
126
  updated_at: z.ZodISODateTime;
76
127
  seq: z.ZodNumber;
@@ -89,6 +140,11 @@ export type JobEvent = z.infer<typeof jobEvent>;
89
140
  /**
90
141
  * What a busy refusal tells the caller: what is already running. A refusal that
91
142
  * only says "busy" forces the caller to guess whether to wait or to give up.
143
+ *
144
+ * `running` is whatever occupies the slug — a `running` job, an `unknown` one
145
+ * the robot has not accounted for yet, or an `external` goal someone else
146
+ * started — and its `state` and `origin` say which, so a caller can tell
147
+ * "wait for it" from "cancel what someone else started".
92
148
  */
93
149
  export declare const busyDetails: z.ZodObject<{
94
150
  running: z.ZodObject<{
@@ -96,12 +152,17 @@ export declare const busyDetails: z.ZodObject<{
96
152
  robot_id: z.ZodUUID;
97
153
  slug: z.ZodString;
98
154
  state: z.ZodEnum<{
155
+ unknown: "unknown";
99
156
  failed: "failed";
100
157
  running: "running";
101
158
  succeeded: "succeeded";
102
159
  cancelled: "cancelled";
103
160
  lost: "lost";
104
161
  }>;
162
+ origin: z.ZodEnum<{
163
+ fleetless: "fleetless";
164
+ external: "external";
165
+ }>;
105
166
  started_at: z.ZodISODateTime;
106
167
  updated_at: z.ZodISODateTime;
107
168
  seq: z.ZodNumber;
@@ -203,6 +264,7 @@ export declare const jobRun: z.ZodObject<{
203
264
  service: "service";
204
265
  }>;
205
266
  state: z.ZodEnum<{
267
+ unknown: "unknown";
206
268
  failed: "failed";
207
269
  running: "running";
208
270
  succeeded: "succeeded";
@@ -239,6 +301,7 @@ export declare const jobRunQuery: z.ZodObject<{
239
301
  robot_id: z.ZodOptional<z.ZodUUID>;
240
302
  slug: z.ZodOptional<z.ZodString>;
241
303
  state: z.ZodOptional<z.ZodEnum<{
304
+ unknown: "unknown";
242
305
  failed: "failed";
243
306
  running: "running";
244
307
  succeeded: "succeeded";
@@ -263,6 +326,7 @@ export declare const jobRunListResponse: z.ZodObject<{
263
326
  service: "service";
264
327
  }>;
265
328
  state: z.ZodEnum<{
329
+ unknown: "unknown";
266
330
  failed: "failed";
267
331
  running: "running";
268
332
  succeeded: "succeeded";
package/dist/jobs.js CHANGED
@@ -11,14 +11,43 @@ 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. **`lost` is a real outcome and must be said out loud.** Job state
15
- * lives only in the bridge's memory; if it restarts mid-job, the results
16
- * are gone. The cloud then marks the job `lost` — never leaves it reading
17
- * "running" because nobody contradicted it. A system that reports a
18
- * machine is still working when it does not know is worse than one that
19
- * admits it lost track.
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
+ * The states a bridge may state about a job: every `jobState` but `unknown`,
29
+ * which is the cloud's own word for not having heard. The bridge's
30
+ * `job_update`, `job_status` entries and `hello.active_jobs` use this, so a
31
+ * bridge claiming `unknown` fails validation instead of parking a job
32
+ * nobody will ever ask about.
33
+ */
34
+ export const reportedJobState = jobState.exclude(['unknown']);
35
+ /**
36
+ * Who started a job.
37
+ *
38
+ * `fleetless` for every job the cloud minted from an invocation. `external`
39
+ * for a goal the bridge found active on a published action without having
40
+ * sent it — started by anyone else on the robot's ROS graph, or the bridge's
41
+ * own goal after its mapping was lost. An external job has the same shape,
42
+ * states, live stream and cancel as any other, but no parameters (ROS 2
43
+ * publishes a goal's request nowhere), no starter, and it lives in memory
44
+ * only: it is never written to `job_runs` and never counts towards quotas.
45
+ */
46
+ export const jobOrigin = z.enum(['fleetless', 'external']);
47
+ /**
48
+ * One job, as the cloud tells every client about it — a Fleetless job or an
49
+ * external goal alike, told apart only by `origin`.
20
50
  */
21
- export const jobState = z.enum(['running', 'succeeded', 'failed', 'cancelled', 'lost']);
22
51
  export const job = z.object({
23
52
  id: z.uuid().meta({
24
53
  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 +57,10 @@ export const job = z.object({
28
57
  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
58
  }),
30
59
  state: jobState.meta({
31
- description: 'Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `lost` is a real outcome — the bridge restarted mid-job and the result is gone — stated rather than left reading `running` by default.',
60
+ 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.',
61
+ }),
62
+ origin: jobOrigin.meta({
63
+ 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
64
  }),
33
65
  started_at: z.iso.datetime().meta({
34
66
  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 +79,7 @@ export const job = z.object({
47
79
  * exists for the same reason on the audit log.
48
80
  *
49
81
  * **Scoped honestly: per cloud process, per run.** Job state lives in memory
50
- * — that is why `lost` exists at all — so this counter restarts when
82
+ * — that is why `unknown` and `lost` exist at all — so this counter restarts when
51
83
  * the cloud does, alongside the jobs it orders. Sound, because it only ever
52
84
  * orders jobs that coexist in one registry — and stated, because a reader
53
85
  * who assumed `auditEvent.seq`'s durable semantics would be wrong.
@@ -85,7 +117,7 @@ export const job = z.object({
85
117
  })
86
118
  .nullable()
87
119
  .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. `null` unless `state` is `failed`.',
120
+ 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
121
  }),
90
122
  });
91
123
  /**
@@ -111,6 +143,11 @@ export const jobEvent = z.object({
111
143
  /**
112
144
  * What a busy refusal tells the caller: what is already running. A refusal that
113
145
  * only says "busy" forces the caller to guess whether to wait or to give up.
146
+ *
147
+ * `running` is whatever occupies the slug — a `running` job, an `unknown` one
148
+ * the robot has not accounted for yet, or an `external` goal someone else
149
+ * started — and its `state` and `origin` say which, so a caller can tell
150
+ * "wait for it" from "cancel what someone else started".
114
151
  */
115
152
  export const busyDetails = z.object({
116
153
  running: job,
@@ -210,16 +247,16 @@ export const jobRun = z.object({
210
247
  description: 'Whether the slug was an `action` or a `service`.',
211
248
  }),
212
249
  state: jobState.meta({
213
- description: 'How the run ended, or `running` while it is still going. `lost` means the bridge restarted mid-run and the outcome is unknowable rather than unknown.',
250
+ 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
251
  }),
215
252
  started_at: z.iso.datetime().meta({
216
253
  description: 'When the run started, as an ISO 8601 timestamp. Runs are listed and filtered by this instant.',
217
254
  }),
218
255
  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.',
256
+ 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
257
  }),
221
258
  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".',
259
+ 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
260
  }),
224
261
  result: z.unknown().nullable().meta({
225
262
  description: 'What the action or service returned once it succeeded, shaped by ROS itself. `null` otherwise.',
@@ -278,7 +315,7 @@ export const jobRunQuery = z
278
315
  description: 'Only runs of this action or service.',
279
316
  }),
280
317
  state: jobState.optional().meta({
281
- description: 'Only runs in this state — `running`, `succeeded`, `failed`, `cancelled` or `lost`.',
318
+ description: 'Only runs in this state — `running`, `unknown`, `succeeded`, `failed`, `cancelled` or `lost`.',
282
319
  }),
283
320
  kind: jobRunKind.optional().meta({
284
321
  description: 'Only `action` runs, or only `service` runs.',