@fleetless/contracts 5.0.0-next.1 → 5.0.0

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
@@ -9,6 +9,8 @@ version.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [5.0.0] — 2026-09-30
13
+
12
14
  ### Added
13
15
 
14
16
  - **Protocol 5: `unknown` jobs, external goals, and a hard cut of protocols 3
@@ -29,13 +31,27 @@ version.
29
31
  `hello_error` for a bridge below protocol 5, naming bridge `6.0.0`; and
30
32
  `job_unknown_to_bridge`, the final `lost` reason when the bridge does not
31
33
  know a job and nothing it cannot attribute runs on the job's action.
34
+ - **A cancel is answered with each goal's `CancelGoal` return code.**
35
+ `cancel` gains a required `request_id`; the bridge answers with the new
36
+ frame `cancel_result` (`bridgeCancelResult`, `bridgeCancelResultEntry`):
37
+ every goal the cancel reached, with its `job_id`, `goal_id` and
38
+ `return_code` (`CANCEL_RETURN_CODES`, `null` when the server did not
39
+ answer). A cancel naming a `job_id` the bridge does not hold cancels every
40
+ external goal on the action, never one of the bridge's own jobs. New error
41
+ code `cancel_rejected`: the server answered `ERROR_REJECTED`, and the
42
+ caller is refused, not told the cancel succeeded. `reportedJobState`, every
43
+ state but `unknown`, is what `job_update`, `job_status` entries and
44
+ `hello.active_jobs` accept, so a bridge claiming `unknown` fails validation.
45
+ `POST /api/robots/:id/jobs/:slug/cancel` lists the answers this adds:
46
+ `409 cancel_rejected`, `504 bridge_timeout`, and `502` with the bridge's
47
+ own code (`unknown_slug`, `action_server_lost`, `internal_error`).
32
48
 
33
49
  ### Removed
34
50
 
35
51
  - **Protocols 2, 3 and 4, and an origin-less `job`; this is why the release
36
52
  is a major.** A bridge below `6.0.0` is refused at `hello`, and a `job` or
37
53
  `job_update` literal without `origin` (or a `job_update` without
38
- `goal_id`) no longer parses.
54
+ `goal_id`) no longer parses, nor does a `cancel` without `request_id`.
39
55
 
40
56
  ## [4.0.0] — 2026-09-29
41
57
 
@@ -7003,7 +7003,7 @@
7003
7003
  }
7004
7004
  },
7005
7005
  "default": {
7006
- "description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `forbidden`, `invalid_uuid`, `not_found`, `validation_error`, `not_cancellable`, `robot_offline`.",
7006
+ "description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `forbidden`, `invalid_uuid`, `not_found`, `validation_error`, `not_cancellable`, `robot_offline`, `cancel_rejected`, `bridge_timeout`, `unknown_slug`, `action_server_lost`, `internal_error`.",
7007
7007
  "content": {
7008
7008
  "application/json": {
7009
7009
  "schema": {
@@ -7013,7 +7013,7 @@
7013
7013
  }
7014
7014
  }
7015
7015
  },
7016
- "description": "The body is optional: a bodyless `POST` was every caller's shape before `job_id` existed, and absent or `job_id: null` both mean \"cancel whatever is running\". A named `job_id` that is **not** what is running cancels nothing and answers `404` — the caller named an id and thereby ruled the other one out. An `external` job is cancelled the same way, through its goal id. Cancelling an `unknown` job also cancels every external goal on its action, since one of them may be that job. A service is `422 not_cancellable`: a service call has no goal to cancel. Nothing running is a `200` with `job: null`.",
7016
+ "description": "The body is optional: a bodyless `POST` was every caller's shape before `job_id` existed, and absent or `job_id: null` both mean \"cancel whatever is running\". A named `job_id` that is **not** what is running cancels nothing and answers `404` — the caller named an id and thereby ruled the other one out. An `external` job is cancelled the same way, through its goal id. Cancelling an `unknown` job also cancels every external goal on its action, since one of them may be that job. A service is `422 not_cancellable`: a service call has no goal to cancel. Nothing running is a `200` with `job: null`. The answer waits for the bridge's `cancel_result`: a `200` means the action server accepted the cancel request, not that the goal has ended — the job's end arrives as its own update. Any goal answered `ERROR_REJECTED` makes it `409 cancel_rejected`, with every goal and its `return_code` in `details.goals`; no answer within `JOB_HEARTBEAT_TIMEOUT_MS` is `504 bridge_timeout`; a cancel the bridge could not send at all is `502` carrying the bridge's own code (`unknown_slug`, `action_server_lost`, `internal_error`).",
7017
7017
  "requestBody": {
7018
7018
  "required": false,
7019
7019
  "content": {
@@ -4147,10 +4147,15 @@
4147
4147
  "not_found",
4148
4148
  "validation_error",
4149
4149
  "not_cancellable",
4150
- "robot_offline"
4150
+ "robot_offline",
4151
+ "cancel_rejected",
4152
+ "bridge_timeout",
4153
+ "unknown_slug",
4154
+ "action_server_lost",
4155
+ "internal_error"
4151
4156
  ],
4152
4157
  "transport": "http",
4153
- "notes": "The body is optional: a bodyless `POST` was every caller's shape before `job_id` existed, and absent or `job_id: null` both mean \"cancel whatever is running\". A named `job_id` that is **not** what is running cancels nothing and answers `404` — the caller named an id and thereby ruled the other one out. An `external` job is cancelled the same way, through its goal id. Cancelling an `unknown` job also cancels every external goal on its action, since one of them may be that job. A service is `422 not_cancellable`: a service call has no goal to cancel. Nothing running is a `200` with `job: null`."
4158
+ "notes": "The body is optional: a bodyless `POST` was every caller's shape before `job_id` existed, and absent or `job_id: null` both mean \"cancel whatever is running\". A named `job_id` that is **not** what is running cancels nothing and answers `404` — the caller named an id and thereby ruled the other one out. An `external` job is cancelled the same way, through its goal id. Cancelling an `unknown` job also cancels every external goal on its action, since one of them may be that job. A service is `422 not_cancellable`: a service call has no goal to cancel. Nothing running is a `200` with `job: null`. The answer waits for the bridge's `cancel_result`: a `200` means the action server accepted the cancel request, not that the goal has ended — the job's end arrives as its own update. Any goal answered `ERROR_REJECTED` makes it `409 cancel_rejected`, with every goal and its `return_code` in `details.goals`; no answer within `JOB_HEARTBEAT_TIMEOUT_MS` is `504 bridge_timeout`; a cancel the bridge could not send at all is `502` carrying the bridge's own code (`unknown_slug`, `action_server_lost`, `internal_error`)."
4154
4159
  },
4155
4160
  {
4156
4161
  "method": "POST",
@@ -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/dist/routes.js CHANGED
@@ -2112,12 +2112,19 @@ export const ROUTES = [
2112
2112
  { name: 'slug', description: 'The action slug from the published configuration; a service slug is refused.' },
2113
2113
  ],
2114
2114
  query: null, request: cancelRequest, requestOptional: true, response: jobResponse,
2115
- errors: [...CLIENT_GUARD, 'invalid_uuid', 'not_found', 'validation_error', 'not_cancellable', 'robot_offline'], transport: 'http',
2115
+ errors: [
2116
+ ...CLIENT_GUARD, 'invalid_uuid', 'not_found', 'validation_error', 'not_cancellable', 'robot_offline',
2117
+ 'cancel_rejected', 'bridge_timeout', 'unknown_slug', 'action_server_lost', 'internal_error',
2118
+ ], transport: 'http',
2116
2119
  notes: 'The body is optional: a bodyless `POST` was every caller\'s shape before `job_id` existed, and absent or `job_id: null` both mean ' +
2117
2120
  '"cancel whatever is running". A named `job_id` that is **not** what is running cancels nothing and answers `404` — the caller named an ' +
2118
2121
  'id and thereby ruled the other one out. An `external` job is cancelled the same way, through its goal id. Cancelling an `unknown` job ' +
2119
2122
  'also cancels every external goal on its action, since one of them may be that job. A service is `422 not_cancellable`: a service call ' +
2120
- 'has no goal to cancel. Nothing running is a `200` with `job: null`.',
2123
+ 'has no goal to cancel. Nothing running is a `200` with `job: null`. The answer waits for the bridge\'s `cancel_result`: a `200` means ' +
2124
+ 'the action server accepted the cancel request, not that the goal has ended — the job\'s end arrives as its own update. Any goal ' +
2125
+ 'answered `ERROR_REJECTED` makes it `409 cancel_rejected`, with every goal and its `return_code` in `details.goals`; no answer within ' +
2126
+ '`JOB_HEARTBEAT_TIMEOUT_MS` is `504 bridge_timeout`; a cancel the bridge could not send at all is `502` carrying the bridge\'s own ' +
2127
+ 'code (`unknown_slug`, `action_server_lost`, `internal_error`).',
2121
2128
  },
2122
2129
  {
2123
2130
  method: 'POST', path: '/api/robots/:id/publishers/:slug', section: 'commands',
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",
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",