@fleetless/contracts 5.0.0-next.1 → 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.
package/CHANGELOG.md CHANGED
@@ -29,13 +29,24 @@ version.
29
29
  `hello_error` for a bridge below protocol 5, naming bridge `6.0.0`; and
30
30
  `job_unknown_to_bridge`, the final `lost` reason when the bridge does not
31
31
  know a job and nothing it cannot attribute runs on the job's action.
32
+ - **A cancel is answered with each goal's `CancelGoal` return code.**
33
+ `cancel` gains a required `request_id`; the bridge answers with the new
34
+ frame `cancel_result` (`bridgeCancelResult`, `bridgeCancelResultEntry`):
35
+ every goal the cancel reached, with its `job_id`, `goal_id` and
36
+ `return_code` (`CANCEL_RETURN_CODES`, `null` when the server did not
37
+ answer). A cancel naming a `job_id` the bridge does not hold cancels every
38
+ external goal on the action, never one of the bridge's own jobs. New error
39
+ code `cancel_rejected`: the server answered `ERROR_REJECTED`, and the
40
+ caller is refused, not told the cancel succeeded. `reportedJobState`, every
41
+ state but `unknown`, is what `job_update`, `job_status` entries and
42
+ `hello.active_jobs` accept, so a bridge claiming `unknown` fails validation.
32
43
 
33
44
  ### Removed
34
45
 
35
46
  - **Protocols 2, 3 and 4, and an origin-less `job`; this is why the release
36
47
  is a major.** A bridge below `6.0.0` is refused at `hello`, and a `job` or
37
48
  `job_update` literal without `origin` (or a `job_update` without
38
- `goal_id`) no longer parses.
49
+ `goal_id`) no longer parses, nor does a `cancel` without `request_id`.
39
50
 
40
51
  ## [4.0.0] — 2026-09-29
41
52
 
@@ -0,0 +1,86 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "type": {
6
+ "type": "string",
7
+ "const": "cancel_result"
8
+ },
9
+ "request_id": {
10
+ "type": "string",
11
+ "minLength": 1,
12
+ "maxLength": 64
13
+ },
14
+ "slug": {
15
+ "type": "string",
16
+ "minLength": 2,
17
+ "maxLength": 63,
18
+ "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
19
+ },
20
+ "goals": {
21
+ "type": "array",
22
+ "items": {
23
+ "type": "object",
24
+ "properties": {
25
+ "job_id": {
26
+ "type": "string",
27
+ "format": "uuid",
28
+ "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
29
+ },
30
+ "goal_id": {
31
+ "type": "string",
32
+ "minLength": 1
33
+ },
34
+ "return_code": {
35
+ "anyOf": [
36
+ {
37
+ "type": "integer",
38
+ "minimum": 0,
39
+ "maximum": 3
40
+ },
41
+ {
42
+ "type": "null"
43
+ }
44
+ ]
45
+ }
46
+ },
47
+ "required": [
48
+ "job_id",
49
+ "goal_id",
50
+ "return_code"
51
+ ]
52
+ }
53
+ },
54
+ "error": {
55
+ "anyOf": [
56
+ {
57
+ "type": "object",
58
+ "properties": {
59
+ "code": {
60
+ "type": "string",
61
+ "minLength": 1
62
+ },
63
+ "message": {
64
+ "type": "string",
65
+ "minLength": 1
66
+ }
67
+ },
68
+ "required": [
69
+ "code",
70
+ "message"
71
+ ]
72
+ },
73
+ {
74
+ "type": "null"
75
+ }
76
+ ]
77
+ }
78
+ },
79
+ "required": [
80
+ "type",
81
+ "request_id",
82
+ "slug",
83
+ "goals",
84
+ "error"
85
+ ]
86
+ }
@@ -41,7 +41,6 @@
41
41
  "type": "string",
42
42
  "enum": [
43
43
  "running",
44
- "unknown",
45
44
  "succeeded",
46
45
  "failed",
47
46
  "cancelled",
@@ -25,7 +25,6 @@
25
25
  "type": "string",
26
26
  "enum": [
27
27
  "running",
28
- "unknown",
29
28
  "succeeded",
30
29
  "failed",
31
30
  "cancelled",
@@ -21,7 +21,6 @@
21
21
  "type": "string",
22
22
  "enum": [
23
23
  "running",
24
- "unknown",
25
24
  "succeeded",
26
25
  "failed",
27
26
  "cancelled",
@@ -6,6 +6,11 @@
6
6
  "type": "string",
7
7
  "const": "cancel"
8
8
  },
9
+ "request_id": {
10
+ "type": "string",
11
+ "minLength": 1,
12
+ "maxLength": 64
13
+ },
9
14
  "slug": {
10
15
  "type": "string",
11
16
  "minLength": 2,
@@ -27,6 +32,7 @@
27
32
  },
28
33
  "required": [
29
34
  "type",
35
+ "request_id",
30
36
  "slug",
31
37
  "job_id"
32
38
  ]
@@ -0,0 +1,89 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "type": {
6
+ "type": "string",
7
+ "const": "cancel_result"
8
+ },
9
+ "request_id": {
10
+ "type": "string",
11
+ "minLength": 1,
12
+ "maxLength": 64
13
+ },
14
+ "slug": {
15
+ "type": "string",
16
+ "minLength": 2,
17
+ "maxLength": 63,
18
+ "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
19
+ },
20
+ "goals": {
21
+ "type": "array",
22
+ "items": {
23
+ "type": "object",
24
+ "properties": {
25
+ "job_id": {
26
+ "type": "string",
27
+ "format": "uuid",
28
+ "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
29
+ },
30
+ "goal_id": {
31
+ "type": "string",
32
+ "minLength": 1
33
+ },
34
+ "return_code": {
35
+ "anyOf": [
36
+ {
37
+ "type": "integer",
38
+ "minimum": 0,
39
+ "maximum": 3
40
+ },
41
+ {
42
+ "type": "null"
43
+ }
44
+ ]
45
+ }
46
+ },
47
+ "required": [
48
+ "job_id",
49
+ "goal_id",
50
+ "return_code"
51
+ ],
52
+ "additionalProperties": false
53
+ }
54
+ },
55
+ "error": {
56
+ "anyOf": [
57
+ {
58
+ "type": "object",
59
+ "properties": {
60
+ "code": {
61
+ "type": "string",
62
+ "minLength": 1
63
+ },
64
+ "message": {
65
+ "type": "string",
66
+ "minLength": 1
67
+ }
68
+ },
69
+ "required": [
70
+ "code",
71
+ "message"
72
+ ],
73
+ "additionalProperties": false
74
+ },
75
+ {
76
+ "type": "null"
77
+ }
78
+ ]
79
+ }
80
+ },
81
+ "required": [
82
+ "type",
83
+ "request_id",
84
+ "slug",
85
+ "goals",
86
+ "error"
87
+ ],
88
+ "additionalProperties": false
89
+ }
@@ -41,7 +41,6 @@
41
41
  "type": "string",
42
42
  "enum": [
43
43
  "running",
44
- "unknown",
45
44
  "succeeded",
46
45
  "failed",
47
46
  "cancelled",
@@ -25,7 +25,6 @@
25
25
  "type": "string",
26
26
  "enum": [
27
27
  "running",
28
- "unknown",
29
28
  "succeeded",
30
29
  "failed",
31
30
  "cancelled",
@@ -21,7 +21,6 @@
21
21
  "type": "string",
22
22
  "enum": [
23
23
  "running",
24
- "unknown",
25
24
  "succeeded",
26
25
  "failed",
27
26
  "cancelled",
package/dist/errors.d.ts CHANGED
@@ -49,5 +49,5 @@ export type ParameterInvalidDetails = z.infer<typeof parameterInvalidDetails>;
49
49
  * list is the shared vocabulary, not a closed set, so a new refusal never
50
50
  * needs a contracts release before it can be reported honestly.
51
51
  */
52
- export declare const ERROR_CODES: readonly ["not_found", "validation_error", "bad_request", "unknown_datapoint", "invalid_token", "protocol_mismatch", "bridge_too_old", "invalid_frame", "duplicate_slug", "reserved_slug", "unknown_slug", "unknown_field_path", "unknown_type", "unknown_topic", "invalid_rate", "invalid_range", "config_conflict", "no_data", "robot_offline", "bridge_timeout", "unauthorized", "forbidden", "invalid_credentials", "token_expired", "token_revoked", "email_taken", "identifier_taken", "weak_password", "account_blocked", "busy", "parameter_invalid", "job_lost", "job_unknown_to_bridge", "action_server_lost", "action_failed", "goal_rejected", "goal_send_failed", "result_failed", "goal_uncontrollable", "bridge_disconnected", "config_changed", "publisher_busy", "unknown_command", "not_subscribable", "camera_offline", "no_snapshot_yet", "live_unavailable", "wrong_kind", "not_recorded", "not_aggregatable", "quota_exceeded", "credential_in_use", "goal_timeout", "robot_in_use", "robot_deletion_partial", "job_queue_full", "invalid_uuid", "rate_limited", "tier_required", "token_spent", "service_timeout", "asset_missing", "dynamic_registration_disabled", "client_limit_reached", "idp_unavailable", "mcp_disabled", "tool_not_available", "capability_required", "last_owner", "target_state_conflict", "signup_closed", "draft_not_a_document", "internal_error", "not_cancellable", "unsupported_media_type", "wrong_browser", "invalid_yaml", "unstorable_yaml", "registration_closed", "domain_not_allowed", "email_unverified", "origin_not_allowed", "template_invalid", "provider_disabled", "provider_misconfigured", "invalid_redirect_uri", "interaction_expired"];
52
+ export declare const ERROR_CODES: readonly ["not_found", "validation_error", "bad_request", "unknown_datapoint", "invalid_token", "protocol_mismatch", "bridge_too_old", "invalid_frame", "duplicate_slug", "reserved_slug", "unknown_slug", "unknown_field_path", "unknown_type", "unknown_topic", "invalid_rate", "invalid_range", "config_conflict", "no_data", "robot_offline", "bridge_timeout", "unauthorized", "forbidden", "invalid_credentials", "token_expired", "token_revoked", "email_taken", "identifier_taken", "weak_password", "account_blocked", "busy", "parameter_invalid", "cancel_rejected", "job_lost", "job_unknown_to_bridge", "action_server_lost", "action_failed", "goal_rejected", "goal_send_failed", "result_failed", "goal_uncontrollable", "bridge_disconnected", "config_changed", "publisher_busy", "unknown_command", "not_subscribable", "camera_offline", "no_snapshot_yet", "live_unavailable", "wrong_kind", "not_recorded", "not_aggregatable", "quota_exceeded", "credential_in_use", "goal_timeout", "robot_in_use", "robot_deletion_partial", "job_queue_full", "invalid_uuid", "rate_limited", "tier_required", "token_spent", "service_timeout", "asset_missing", "dynamic_registration_disabled", "client_limit_reached", "idp_unavailable", "mcp_disabled", "tool_not_available", "capability_required", "last_owner", "target_state_conflict", "signup_closed", "draft_not_a_document", "internal_error", "not_cancellable", "unsupported_media_type", "wrong_browser", "invalid_yaml", "unstorable_yaml", "registration_closed", "domain_not_allowed", "email_unverified", "origin_not_allowed", "template_invalid", "provider_disabled", "provider_misconfigured", "invalid_redirect_uri", "interaction_expired"];
53
53
  export type ErrorCode = (typeof ERROR_CODES)[number];
package/dist/errors.js CHANGED
@@ -156,6 +156,14 @@ export const ERROR_CODES = [
156
156
  'busy',
157
157
  /** A parameter failed its declared rule; details name the field and the rule. */
158
158
  'parameter_invalid',
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',
159
167
  /**
160
168
  * The bridge's own statement, while connected, that it lost track of a job
161
169
  * it still names — the vocabulary behind its `job_lost` frame. Distinct
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, 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, CloudPublish, BridgeJobUpdate, BridgeJobLost, CloudJobQuery, BridgeJobStatusEntry, BridgeJobStatus, SnapshotHeader, CloudCameraStart, CloudCameraStop, BridgeCameraState, ActiveJob, BridgeAssetsAvailable, CloudAssetRequest, BridgeAssetProgress, } from './protocol.js';
8
- export { jobState, jobOrigin, job, jobEvent, busyDetails, publisherBusyDetails, jobQueueFullDetails } from './jobs.js';
9
- export type { JobState, JobOrigin, 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, cloudJobQuery, bridgeJobStatusEntry, bridgeJobStatus, 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, jobOrigin, 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
@@ -31,6 +31,21 @@ export declare const jobState: z.ZodEnum<{
31
31
  lost: "lost";
32
32
  }>;
33
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>;
34
49
  /**
35
50
  * Who started a job.
36
51
  *
package/dist/jobs.js CHANGED
@@ -24,6 +24,14 @@ import { slug, wireSeqCursor, wireTimestampMs } from './common.js';
24
24
  * back and says it finished.
25
25
  */
26
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']);
27
35
  /**
28
36
  * Who started a job.
29
37
  *
@@ -221,13 +221,18 @@ export { slug } from './common.js';
221
221
  * has a terminal result still in hand reports it here and the cloud writes it
222
222
  * down, instead of publishing `lost` over a job that in fact succeeded.
223
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.
224
+ * bridge listing a job has, by definition, something to say about it — the
225
+ * schema refuses it.
226
+ *
227
+ * Only the bridge's own jobs, never an external goal: this entry carries no
228
+ * `origin`, and a cloud adopting an external goal from it would take it for
229
+ * its own. An active external goal is reported by the next `job_update`
230
+ * tick, with its origin.
225
231
  */
226
232
  export declare const activeJob: z.ZodObject<{
227
233
  job_id: z.ZodUUID;
228
234
  slug: z.ZodString;
229
235
  state: z.ZodEnum<{
230
- unknown: "unknown";
231
236
  failed: "failed";
232
237
  running: "running";
233
238
  succeeded: "succeeded";
@@ -246,7 +251,6 @@ export declare const bridgeHello: z.ZodObject<{
246
251
  job_id: z.ZodUUID;
247
252
  slug: z.ZodString;
248
253
  state: z.ZodEnum<{
249
- unknown: "unknown";
250
254
  failed: "failed";
251
255
  running: "running";
252
256
  succeeded: "succeeded";
@@ -625,18 +629,79 @@ export type CloudInvoke = z.infer<typeof cloudInvoke>;
625
629
  * wire had nowhere to put it.
626
630
  *
627
631
  * `null` keeps today's meaning and must be read as exactly that: *cancel
628
- * whatever is running on this slug*. It is a real request — an operator
629
- * hitting stop wants the robot stopped, not a lecture about job identity —
630
- * and it stays available for that. A bridge given an id that does not match
631
- * what is running cancels **nothing** and says so; it must not fall back to
632
- * the slug, because a caller who named an id has ruled that out.
632
+ * every goal active on this slug's action*. It is a real request — an
633
+ * operator hitting stop wants the robot stopped, not a lecture about job
634
+ * identity — and it stays available for that.
635
+ *
636
+ * A `job_id` the bridge holds (its own job, or an external goal's derived
637
+ * id) cancels that goal alone. A `job_id` it does not hold is how the cloud
638
+ * cancels an `unknown` job: the bridge cancels every **external** goal
639
+ * active on the slug's action, since one of them may be that job, and never
640
+ * one of its own jobs, which the caller did not name.
641
+ *
642
+ * `request_id` correlates the bridge's `cancel_result`, which carries each
643
+ * goal's `CancelGoal` return code; the cloud answers its caller only from
644
+ * that.
633
645
  */
634
646
  export declare const cloudCancel: z.ZodObject<{
635
647
  type: z.ZodLiteral<"cancel">;
648
+ request_id: z.ZodString;
636
649
  slug: z.ZodString;
637
650
  job_id: z.ZodNullable<z.ZodUUID>;
638
651
  }, z.core.$strip>;
639
652
  export type CloudCancel = z.infer<typeof cloudCancel>;
653
+ /**
654
+ * The ROS 2 `action_msgs/srv/CancelGoal` return codes: `0` `ERROR_NONE` (the
655
+ * server accepted the cancel request), `1` `ERROR_REJECTED` (it refused),
656
+ * `2` `ERROR_UNKNOWN_GOAL_ID`, `3` `ERROR_GOAL_TERMINATED` (the goal had
657
+ * already ended). An accepted request is not an ended goal: whether the goal
658
+ * ends, and how, is what the action's status reports afterwards, and reaches
659
+ * the cloud as the goal's `job_update`.
660
+ */
661
+ export declare const CANCEL_RETURN_CODES: {
662
+ readonly none: 0;
663
+ readonly rejected: 1;
664
+ readonly unknown_goal_id: 2;
665
+ readonly goal_terminated: 3;
666
+ };
667
+ export declare const cancelReturnCode: z.ZodNumber;
668
+ export type CancelReturnCode = z.infer<typeof cancelReturnCode>;
669
+ /** One goal a `cancel` reached, and what its action server answered. */
670
+ export declare const bridgeCancelResultEntry: z.ZodObject<{
671
+ job_id: z.ZodUUID;
672
+ goal_id: z.ZodString;
673
+ return_code: z.ZodNullable<z.ZodNumber>;
674
+ }, z.core.$strip>;
675
+ export type BridgeCancelResultEntry = z.infer<typeof bridgeCancelResultEntry>;
676
+ /**
677
+ * The bridge's answer to a `cancel`, once every goal it sent a cancel request
678
+ * for has answered — or `error` when it could not ask. `goals` is empty when
679
+ * nothing matched (a `job_id` the bridge does not hold, with no external goal
680
+ * on the action; or nothing active on the slug): the cancel reached nothing,
681
+ * which is an answer, not a failure.
682
+ *
683
+ * A goal whose server did not answer the cancel request within the bridge's
684
+ * own bound is listed with `return_code: null`, not left out.
685
+ *
686
+ * The cloud reports `return_code` `1` (`ERROR_REJECTED`) to the caller as a
687
+ * refusal (`cancel_rejected`), never as success, and writes the audit event
688
+ * of a cancel of an external goal from this frame, return code included.
689
+ */
690
+ export declare const bridgeCancelResult: z.ZodObject<{
691
+ type: z.ZodLiteral<"cancel_result">;
692
+ request_id: z.ZodString;
693
+ slug: z.ZodString;
694
+ goals: z.ZodArray<z.ZodObject<{
695
+ job_id: z.ZodUUID;
696
+ goal_id: z.ZodString;
697
+ return_code: z.ZodNullable<z.ZodNumber>;
698
+ }, z.core.$strip>>;
699
+ error: z.ZodNullable<z.ZodObject<{
700
+ code: z.ZodString;
701
+ message: z.ZodString;
702
+ }, z.core.$strip>>;
703
+ }, z.core.$strip>;
704
+ export type BridgeCancelResult = z.infer<typeof bridgeCancelResult>;
640
705
  export declare const cloudPublish: z.ZodObject<{
641
706
  type: z.ZodLiteral<"publish">;
642
707
  slug: z.ZodString;
@@ -666,7 +731,6 @@ export declare const bridgeJobUpdate: z.ZodObject<{
666
731
  job_id: z.ZodUUID;
667
732
  slug: z.ZodString;
668
733
  state: z.ZodEnum<{
669
- unknown: "unknown";
670
734
  failed: "failed";
671
735
  running: "running";
672
736
  succeeded: "succeeded";
@@ -729,11 +793,15 @@ export declare const cloudJobQuery: z.ZodObject<{
729
793
  job_ids: z.ZodArray<z.ZodUUID>;
730
794
  }, z.core.$strip>;
731
795
  export type CloudJobQuery = z.infer<typeof cloudJobQuery>;
732
- /** One job the bridge recognises, in a `job_status` answer. Same fields as `job_update`'s. */
796
+ /**
797
+ * One job the bridge recognises, in a `job_status` answer: its state, feedback,
798
+ * progress, result and error, as a `job_update` would carry them. No `slug`,
799
+ * `origin`, `goal_id` or `timestamp_ms` — the cloud asked about a job it
800
+ * already holds, so it knows the first three, and the answer is current.
801
+ */
733
802
  export declare const bridgeJobStatusEntry: z.ZodObject<{
734
803
  job_id: z.ZodUUID;
735
804
  state: z.ZodEnum<{
736
- unknown: "unknown";
737
805
  failed: "failed";
738
806
  running: "running";
739
807
  succeeded: "succeeded";
@@ -767,7 +835,6 @@ export declare const bridgeJobStatus: z.ZodObject<{
767
835
  jobs: z.ZodArray<z.ZodObject<{
768
836
  job_id: z.ZodUUID;
769
837
  state: z.ZodEnum<{
770
- unknown: "unknown";
771
838
  failed: "failed";
772
839
  running: "running";
773
840
  succeeded: "succeeded";
package/dist/protocol.js CHANGED
@@ -4,7 +4,7 @@ import { assetFailure } from './assets.js';
4
4
  import { applyError, slug } from './common.js';
5
5
  import { robotConfigDoc } from './config.js';
6
6
  import { rosGraph, typeDefinition } from './introspection.js';
7
- import { jobOrigin, jobState } from './jobs.js';
7
+ import { jobOrigin, reportedJobState } from './jobs.js';
8
8
  import { rosTypeName } from './common.js';
9
9
  /**
10
10
  * Bridge <-> cloud protocol, version 5.
@@ -238,12 +238,18 @@ export { slug } from './common.js';
238
238
  * has a terminal result still in hand reports it here and the cloud writes it
239
239
  * down, instead of publishing `lost` over a job that in fact succeeded.
240
240
  * Never `unknown`: that is the cloud's word for not having heard, and a
241
- * bridge listing a job has, by definition, something to say about it.
241
+ * bridge listing a job has, by definition, something to say about it — the
242
+ * schema refuses it.
243
+ *
244
+ * Only the bridge's own jobs, never an external goal: this entry carries no
245
+ * `origin`, and a cloud adopting an external goal from it would take it for
246
+ * its own. An active external goal is reported by the next `job_update`
247
+ * tick, with its origin.
242
248
  */
243
249
  export const activeJob = z.object({
244
250
  job_id: z.uuid(),
245
251
  slug,
246
- state: jobState,
252
+ state: reportedJobState,
247
253
  });
248
254
  /** First frame a bridge sends after the socket opens. */
249
255
  export const bridgeHello = z.object({
@@ -462,17 +468,67 @@ export const cloudInvoke = z.object({
462
468
  * wire had nowhere to put it.
463
469
  *
464
470
  * `null` keeps today's meaning and must be read as exactly that: *cancel
465
- * whatever is running on this slug*. It is a real request — an operator
466
- * hitting stop wants the robot stopped, not a lecture about job identity —
467
- * and it stays available for that. A bridge given an id that does not match
468
- * what is running cancels **nothing** and says so; it must not fall back to
469
- * the slug, because a caller who named an id has ruled that out.
471
+ * every goal active on this slug's action*. It is a real request — an
472
+ * operator hitting stop wants the robot stopped, not a lecture about job
473
+ * identity — and it stays available for that.
474
+ *
475
+ * A `job_id` the bridge holds (its own job, or an external goal's derived
476
+ * id) cancels that goal alone. A `job_id` it does not hold is how the cloud
477
+ * cancels an `unknown` job: the bridge cancels every **external** goal
478
+ * active on the slug's action, since one of them may be that job, and never
479
+ * one of its own jobs, which the caller did not name.
480
+ *
481
+ * `request_id` correlates the bridge's `cancel_result`, which carries each
482
+ * goal's `CancelGoal` return code; the cloud answers its caller only from
483
+ * that.
470
484
  */
471
485
  export const cloudCancel = z.object({
472
486
  type: z.literal('cancel'),
487
+ request_id: z.string().min(1).max(64),
473
488
  slug,
474
489
  job_id: z.uuid().nullable(),
475
490
  });
491
+ /**
492
+ * The ROS 2 `action_msgs/srv/CancelGoal` return codes: `0` `ERROR_NONE` (the
493
+ * server accepted the cancel request), `1` `ERROR_REJECTED` (it refused),
494
+ * `2` `ERROR_UNKNOWN_GOAL_ID`, `3` `ERROR_GOAL_TERMINATED` (the goal had
495
+ * already ended). An accepted request is not an ended goal: whether the goal
496
+ * ends, and how, is what the action's status reports afterwards, and reaches
497
+ * the cloud as the goal's `job_update`.
498
+ */
499
+ export const CANCEL_RETURN_CODES = { none: 0, rejected: 1, unknown_goal_id: 2, goal_terminated: 3 };
500
+ export const cancelReturnCode = z.number().int().min(0).max(3);
501
+ /** One goal a `cancel` reached, and what its action server answered. */
502
+ export const bridgeCancelResultEntry = z.object({
503
+ /** The job the goal belongs to — the bridge's own, or an external goal's derived id. */
504
+ job_id: z.uuid(),
505
+ /** The ROS 2 goal id the cancel was sent for. */
506
+ goal_id: z.string().min(1),
507
+ /** `null` when the action server did not answer the cancel request within the bridge's own bound. */
508
+ return_code: cancelReturnCode.nullable(),
509
+ });
510
+ /**
511
+ * The bridge's answer to a `cancel`, once every goal it sent a cancel request
512
+ * for has answered — or `error` when it could not ask. `goals` is empty when
513
+ * nothing matched (a `job_id` the bridge does not hold, with no external goal
514
+ * on the action; or nothing active on the slug): the cancel reached nothing,
515
+ * which is an answer, not a failure.
516
+ *
517
+ * A goal whose server did not answer the cancel request within the bridge's
518
+ * own bound is listed with `return_code: null`, not left out.
519
+ *
520
+ * The cloud reports `return_code` `1` (`ERROR_REJECTED`) to the caller as a
521
+ * refusal (`cancel_rejected`), never as success, and writes the audit event
522
+ * of a cancel of an external goal from this frame, return code included.
523
+ */
524
+ export const bridgeCancelResult = z.object({
525
+ type: z.literal('cancel_result'),
526
+ request_id: z.string().min(1).max(64),
527
+ slug,
528
+ goals: z.array(bridgeCancelResultEntry),
529
+ /** Set when the bridge could not send the cancel at all (no such slug, a service, the server gone). */
530
+ error: z.object({ code: z.string().min(1), message: z.string().min(1) }).nullable(),
531
+ });
476
532
  export const cloudPublish = z.object({
477
533
  type: z.literal('publish'),
478
534
  slug,
@@ -511,7 +567,7 @@ export const bridgeJobUpdate = z.object({
511
567
  job_id: z.uuid(),
512
568
  slug,
513
569
  /** Never `unknown`: that is the cloud's word for not having heard. */
514
- state: jobState,
570
+ state: reportedJobState,
515
571
  origin: jobOrigin,
516
572
  /** The ROS 2 goal id; `null` for a service job, which has no goal. */
517
573
  goal_id: z.string().min(1).nullable(),
@@ -559,11 +615,16 @@ export const cloudJobQuery = z.object({
559
615
  request_id: z.string().min(1).max(64),
560
616
  job_ids: z.array(z.uuid()).min(1),
561
617
  });
562
- /** One job the bridge recognises, in a `job_status` answer. Same fields as `job_update`'s. */
618
+ /**
619
+ * One job the bridge recognises, in a `job_status` answer: its state, feedback,
620
+ * progress, result and error, as a `job_update` would carry them. No `slug`,
621
+ * `origin`, `goal_id` or `timestamp_ms` — the cloud asked about a job it
622
+ * already holds, so it knows the first three, and the answer is current.
623
+ */
563
624
  export const bridgeJobStatusEntry = z.object({
564
625
  job_id: z.uuid(),
565
626
  /** Never `unknown` — the bridge only ever states a definite fact about a job it recognises. */
566
- state: jobState,
627
+ state: reportedJobState,
567
628
  feedback: z.unknown().nullable(),
568
629
  progress: z.number().min(0).max(1).nullable(),
569
630
  result: z.unknown().nullable(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetless/contracts",
3
- "version": "5.0.0-next.1",
3
+ "version": "5.0.0-next.2",
4
4
  "description": "Fleetless wire contracts: the bridge-cloud protocol, the REST API schemas and the error codes, as zod schemas with generated JSON Schema and OpenAPI artifacts.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dehne Robotik GmbH",