@fleetless/contracts 2.0.0 → 4.0.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -1
- package/CONTRIBUTING.md +47 -30
- package/artifacts/constants.json +10 -2
- package/artifacts/openapi.json +301 -30
- package/artifacts/routes.json +124 -5
- package/artifacts/schema/app-deletion-summary.schema.json +51 -0
- package/artifacts/schema/bridge-job-lost.schema.json +17 -0
- package/artifacts/schema/put-app-auth-mcp-request.schema.json +27 -0
- package/artifacts/schema/put-app-auth-registration-request.schema.json +36 -0
- package/artifacts/schema/put-app-auth-urls-request.schema.json +48 -0
- package/artifacts/schema-outgoing/bridge-job-lost.schema.json +18 -0
- package/dist/app-users.d.ts +25 -7
- package/dist/app-users.js +24 -6
- package/dist/apps.d.ts +22 -0
- package/dist/apps.js +33 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +38 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/protocol.d.ts +67 -6
- package/dist/protocol.js +66 -7
- package/dist/realtime.d.ts +2 -2
- package/dist/rest.d.ts +2 -0
- package/dist/rest.js +2 -0
- package/dist/routes.js +72 -13
- package/package.json +1 -1
- package/artifacts/schema/put-app-auth-config-request.schema.json +0 -93
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ 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, } from './protocol.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
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
8
|
export { jobState, job, jobEvent, busyDetails, publisherBusyDetails, jobQueueFullDetails } from './jobs.js';
|
|
9
9
|
export type { JobState, Job, JobEvent, BusyDetails, PublisherBusyDetails, JobQueueFullDetails, } from './jobs.js';
|
|
@@ -31,14 +31,14 @@ export { clientAuth, authOk, authError, clientInvoke, clientCancel, clientPublis
|
|
|
31
31
|
export type { ClientAuth, AuthOk, AuthError, ClientInvoke, ClientCancel, ClientPublish, CommandResult, ErrorFrame, ClientSubscribe, ClientUnsubscribe, SubscribeError, DatapointEvent, ResourceHealthEvent, ResourceHealthCleared, LiveSessionEndReason, LiveSessionEvent, OrgEventKind, OrgEventSeverity, OrgEvent, OrgEventSubscribe, OrgEventUnsubscribe, OrgEventReplay, OrgEventDropped, } from './realtime.js';
|
|
32
32
|
export { password, org, patchOrgResponse, sessionTokens, refreshRequest, signUpRequest, signUpResponse, waitlistRequest, developerLoginRequest, USER_DISPLAY_NAME_MAX, orgAdminTier, fleetlessUser, fleetlessUserListResponse, createTeamInviteRequest, teamInvite, pendingTeamInvite, pendingTeamInviteListResponse, acceptTeamInviteRequest, patchFleetlessUserRequest, tierChangeRequest, mailStatus, tierRequiredDetails, passwordChangeRequest, passwordResetRequest, passwordResetConfirm, idpIssuer, authMeResponse, patchOrgRequest, patchAuthMeRequest, } from './identity.js';
|
|
33
33
|
export type { Org, PatchOrgResponse, SessionTokens, RefreshRequest, SignUpRequest, SignUpResponse, WaitlistRequest, DeveloperLoginRequest, OrgAdminTier, FleetlessUser, FleetlessUserListResponse, CreateTeamInviteRequest, TeamInvite, PendingTeamInvite, PendingTeamInviteListResponse, AcceptTeamInviteRequest, PatchFleetlessUserRequest, TierChangeRequest, MailStatus, TierRequiredDetails, PasswordChangeRequest, PasswordResetRequest, PasswordResetConfirm, IdpIssuer, AuthMeResponse, PatchOrgRequest, PatchAuthMeRequest, } from './identity.js';
|
|
34
|
-
export { appIdentifier, app, appListResponse, createAppRequest, updateAppRequest, serverKeyToken, serverKey, serverKeyListResponse, createServerKeyResponse, role, roleListResponse, rolePermissions, } from './apps.js';
|
|
35
|
-
export type { App, AppListResponse, CreateAppRequest, UpdateAppRequest, ServerKey, ServerKeyListResponse, CreateServerKeyResponse, Role, RoleListResponse, RolePermissions, } from './apps.js';
|
|
34
|
+
export { appIdentifier, app, appListResponse, appDeletionSummary, createAppRequest, updateAppRequest, serverKeyToken, serverKey, serverKeyListResponse, createServerKeyResponse, role, roleListResponse, rolePermissions, } from './apps.js';
|
|
35
|
+
export type { App, AppListResponse, AppDeletionSummary, CreateAppRequest, UpdateAppRequest, ServerKey, ServerKeyListResponse, CreateServerKeyResponse, Role, RoleListResponse, RolePermissions, } from './apps.js';
|
|
36
36
|
export { clientLoginRequest, clientRefreshRequest, clientLogoutRequest, clientRegisterRequest, clientVerifyEmailRequest, clientResendVerificationRequest, clientPasswordResetRequest, clientPasswordResetConfirmRequest, clientAcceptInvitationRequest, CLIENT_OIDC_CALLBACK_PATH, clientProviderListQuery, clientProviderListResponse, clientOidcStartQuery, clientOidcCallbackQuery, clientOidcExchangeRequest, clientOidcErrorCode, clientMcpInteraction, clientMcpInteractionDecisionResponse, mcpConsentGrant, mcpConsentGrantListResponse, clientIdentity, } from './client-auth.js';
|
|
37
37
|
export type { ClientLoginRequest, ClientRefreshRequest, ClientLogoutRequest, ClientRegisterRequest, ClientVerifyEmailRequest, ClientResendVerificationRequest, ClientPasswordResetRequest, ClientPasswordResetConfirmRequest, ClientAcceptInvitationRequest, ClientProviderListQuery, ClientProviderListResponse, ClientOidcStartQuery, ClientOidcCallbackQuery, ClientOidcExchangeRequest, ClientOidcErrorCode, ClientMcpInteraction, ClientMcpInteractionDecisionResponse, McpConsentGrant, McpConsentGrantListResponse, ClientIdentity, } from './client-auth.js';
|
|
38
38
|
export { clientRobotListItem, clientRobotListResponse } from './client-robots.js';
|
|
39
39
|
export type { ClientRobotListItem, ClientRobotListResponse } from './client-robots.js';
|
|
40
|
-
export { APP_USER_DISPLAY_NAME_MAX, APP_URL_PLACEHOLDERS, MAIL_TEMPLATE_VARIABLES, DEFAULT_MAIL_TEMPLATES, providerSlug, appUserStatus, appUser, appUserListResponse, createAppUserRequest, patchAppUserRequest, createAppInvitationRequest, appInvitation, pendingAppInvitation, appInvitationListResponse, appOidcProvider, appOidcProviderListResponse, createAppOidcProviderRequest, patchAppOidcProviderRequest, appUrlTemplate, allowedOrigin, emailDomain, appAuthConfig,
|
|
41
|
-
export type { AppUserStatus, AppUser, AppUserListResponse, CreateAppUserRequest, PatchAppUserRequest, CreateAppInvitationRequest, AppInvitation, PendingAppInvitation, AppInvitationListResponse, AppOidcProvider, AppOidcProviderListResponse, CreateAppOidcProviderRequest, PatchAppOidcProviderRequest, AppAuthConfig,
|
|
40
|
+
export { APP_USER_DISPLAY_NAME_MAX, APP_URL_PLACEHOLDERS, MAIL_TEMPLATE_VARIABLES, DEFAULT_MAIL_TEMPLATES, providerSlug, appUserStatus, appUser, appUserListResponse, createAppUserRequest, patchAppUserRequest, createAppInvitationRequest, appInvitation, pendingAppInvitation, appInvitationListResponse, appOidcProvider, appOidcProviderListResponse, createAppOidcProviderRequest, patchAppOidcProviderRequest, appUrlTemplate, allowedOrigin, emailDomain, appAuthConfig, putAppAuthRegistrationRequest, putAppAuthUrlsRequest, putAppAuthMcpRequest, mailTemplateKind, appMailTemplate, appMailTemplateListResponse, putAppMailTemplateRequest, mailTemplatePreviewRequest, mailTemplatePreviewResponse, mailTemplateProblemDetails, mailOutcome, } from './app-users.js';
|
|
41
|
+
export type { AppUserStatus, AppUser, AppUserListResponse, CreateAppUserRequest, PatchAppUserRequest, CreateAppInvitationRequest, AppInvitation, PendingAppInvitation, AppInvitationListResponse, AppOidcProvider, AppOidcProviderListResponse, CreateAppOidcProviderRequest, PatchAppOidcProviderRequest, AppAuthConfig, PutAppAuthRegistrationRequest, PutAppAuthUrlsRequest, PutAppAuthMcpRequest, MailTemplateKind, AppMailTemplate, AppMailTemplateListResponse, PutAppMailTemplateRequest, MailTemplatePreviewRequest, MailTemplatePreviewResponse, MailTemplateProblemDetails, MailOutcome, } from './app-users.js';
|
|
42
42
|
export { assetKind, URDF_ASSET_NAME, asset, urdfCompleteness, assetListResponse, assetsClearResponse, missingAssetQuery, assetSyncRequest, assetSyncResponse, assetSyncState, assetSyncStatus, assetFailure, assetFailureKind, assetStoreRefusedDetails, assetSyncBusyDetails, ROBOT_ASSET_STORE_BYTES, } from './assets.js';
|
|
43
43
|
export type { AssetKind, Asset, UrdfCompleteness, AssetListResponse, AssetsClearResponse, MissingAssetQuery, AssetSyncRequest, AssetSyncResponse, AssetSyncState, AssetSyncStatus, AssetFailure, AssetFailureKind, AssetStoreRefusedDetails, AssetSyncBusyDetails, } from './assets.js';
|
|
44
44
|
export { auditActor, auditEvent, auditQuery, auditListResponse, AUDIT_CSV_COLUMNS, AUDIT_RETENTION_DAYS } from './audit.js';
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { PROTOCOL_VERSION, PROTOCOL_VERSIONS, PROTOCOL_SUNSET_DAYS, LATEST_BRIDG
|
|
|
5
5
|
// Assets.
|
|
6
6
|
bridgeAssetsAvailable, cloudAssetRequest, bridgeAssetProgress,
|
|
7
7
|
// Addressing.
|
|
8
|
-
activeJob, DEFAULT_PATIENCE_MS, MAX_PATIENCE_MS, MIN_PATIENCE_MS, } from './protocol.js';
|
|
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
9
|
export { jobState, 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,
|
|
@@ -38,11 +38,11 @@ USER_DISPLAY_NAME_MAX, orgAdminTier, fleetlessUser, fleetlessUserListResponse, c
|
|
|
38
38
|
mailStatus, tierRequiredDetails, passwordChangeRequest, passwordResetRequest, passwordResetConfirm, idpIssuer,
|
|
39
39
|
// auth/me, org and member patches.
|
|
40
40
|
authMeResponse, patchOrgRequest, patchAuthMeRequest, } from './identity.js';
|
|
41
|
-
export { appIdentifier, app, appListResponse, createAppRequest, updateAppRequest, serverKeyToken, serverKey, serverKeyListResponse, createServerKeyResponse, role, roleListResponse, rolePermissions, } from './apps.js';
|
|
41
|
+
export { appIdentifier, app, appListResponse, appDeletionSummary, createAppRequest, updateAppRequest, serverKeyToken, serverKey, serverKeyListResponse, createServerKeyResponse, role, roleListResponse, rolePermissions, } from './apps.js';
|
|
42
42
|
export { clientLoginRequest, clientRefreshRequest, clientLogoutRequest, clientRegisterRequest, clientVerifyEmailRequest, clientResendVerificationRequest, clientPasswordResetRequest, clientPasswordResetConfirmRequest, clientAcceptInvitationRequest, CLIENT_OIDC_CALLBACK_PATH, clientProviderListQuery, clientProviderListResponse, clientOidcStartQuery, clientOidcCallbackQuery, clientOidcExchangeRequest, clientOidcErrorCode, clientMcpInteraction, clientMcpInteractionDecisionResponse, mcpConsentGrant, mcpConsentGrantListResponse, clientIdentity, } from './client-auth.js';
|
|
43
43
|
export { clientRobotListItem, clientRobotListResponse } from './client-robots.js';
|
|
44
44
|
// The per-app identity space.
|
|
45
|
-
export { APP_USER_DISPLAY_NAME_MAX, APP_URL_PLACEHOLDERS, MAIL_TEMPLATE_VARIABLES, DEFAULT_MAIL_TEMPLATES, providerSlug, appUserStatus, appUser, appUserListResponse, createAppUserRequest, patchAppUserRequest, createAppInvitationRequest, appInvitation, pendingAppInvitation, appInvitationListResponse, appOidcProvider, appOidcProviderListResponse, createAppOidcProviderRequest, patchAppOidcProviderRequest, appUrlTemplate, allowedOrigin, emailDomain, appAuthConfig,
|
|
45
|
+
export { APP_USER_DISPLAY_NAME_MAX, APP_URL_PLACEHOLDERS, MAIL_TEMPLATE_VARIABLES, DEFAULT_MAIL_TEMPLATES, providerSlug, appUserStatus, appUser, appUserListResponse, createAppUserRequest, patchAppUserRequest, createAppInvitationRequest, appInvitation, pendingAppInvitation, appInvitationListResponse, appOidcProvider, appOidcProviderListResponse, createAppOidcProviderRequest, patchAppOidcProviderRequest, appUrlTemplate, allowedOrigin, emailDomain, appAuthConfig, putAppAuthRegistrationRequest, putAppAuthUrlsRequest, putAppAuthMcpRequest, mailTemplateKind, appMailTemplate, appMailTemplateListResponse, putAppMailTemplateRequest, mailTemplatePreviewRequest, mailTemplatePreviewResponse, mailTemplateProblemDetails, mailOutcome, } from './app-users.js';
|
|
46
46
|
export { assetKind, URDF_ASSET_NAME, asset, urdfCompleteness, assetListResponse, assetsClearResponse, missingAssetQuery, assetSyncRequest, assetSyncResponse, assetSyncState, assetSyncStatus, assetFailure, assetFailureKind, assetStoreRefusedDetails, assetSyncBusyDetails, ROBOT_ASSET_STORE_BYTES, } from './assets.js';
|
|
47
47
|
export { auditActor, auditEvent, auditQuery, auditListResponse, AUDIT_CSV_COLUMNS, AUDIT_RETENTION_DAYS } from './audit.js';
|
|
48
48
|
export { alertRowCondition, alertSeverity, alertState, datapointAlertRow, alertListResponse, orgFiringAlertsResponse, orgAlertsQuery, datapointDisplay, putDatapointDisplayRequest, } from './alerts.js';
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
/**
|
|
4
|
-
* Bridge <-> cloud protocol, version
|
|
4
|
+
* Bridge <-> cloud protocol, version 4.
|
|
5
5
|
*
|
|
6
6
|
* The version is exchanged in the hello handshake. Since 2026-09 the cloud
|
|
7
7
|
* serves a **window** of versions, not one: every entry of
|
|
@@ -11,6 +11,18 @@ import { z } from 'zod';
|
|
|
11
11
|
* which names the window and reaches the robot's detail view as
|
|
12
12
|
* `last_hello_error`.
|
|
13
13
|
*
|
|
14
|
+
* **4 (2026-09-28):** the bridge sends a `job_update` heartbeat at
|
|
15
|
+
* `JOB_HEARTBEAT_INTERVAL_MS` for every running job, whether or not the
|
|
16
|
+
* action said anything new, and reports a vanished action server with
|
|
17
|
+
* `job_lost`'s new optional `error`, `action_server_lost`. The cloud bounds
|
|
18
|
+
* a protocol-4 job's silence by the heartbeat (`JOB_HEARTBEAT_TIMEOUT_MS`)
|
|
19
|
+
* once it has heard from the job at all; `patience_ms` still bounds
|
|
20
|
+
* acceptance, the same as before. Offline tolerance is
|
|
21
|
+
* `JOB_OFFLINE_GRACE_MS` (five minutes), up from the informal one minute a
|
|
22
|
+
* protocol-3 bridge got. A protocol-3 bridge sends no heartbeat and keeps
|
|
23
|
+
* today's behaviour exactly: `patience_ms` alone bounds the whole running
|
|
24
|
+
* job, silence included.
|
|
25
|
+
*
|
|
14
26
|
* **3 (2026-09-22):** the ping carries `latency_ms` and `lag_ms`, the bridge
|
|
15
27
|
* sends `link_mode`, `bridge_state` gains `low_bandwidth`, and the
|
|
16
28
|
* `bridge_pressure` datapoint is gone. A protocol-2 bridge is served until
|
|
@@ -23,7 +35,7 @@ import { z } from 'zod';
|
|
|
23
35
|
* **2 (2026-08-21):** `config_applied.errors` entries gained `kind` and `code`
|
|
24
36
|
* beside `message`.
|
|
25
37
|
*/
|
|
26
|
-
export declare const PROTOCOL_VERSION =
|
|
38
|
+
export declare const PROTOCOL_VERSION = 4;
|
|
27
39
|
/** Days between a version's deprecation and its sunset. */
|
|
28
40
|
export declare const PROTOCOL_SUNSET_DAYS = 90;
|
|
29
41
|
export interface ProtocolVersionEntry {
|
|
@@ -36,13 +48,15 @@ export interface ProtocolVersionEntry {
|
|
|
36
48
|
/**
|
|
37
49
|
* Every protocol version the cloud has served, oldest first. A test keeps
|
|
38
50
|
* exactly one entry current and equal to `PROTOCOL_VERSION`; `test/changelog.test.ts`
|
|
39
|
-
* requires
|
|
40
|
-
*
|
|
41
|
-
*
|
|
51
|
+
* requires some CHANGELOG section — `[Unreleased]` or a dated one — to name
|
|
52
|
+
* the newest `bridge_from` together with the previous entry's `sunsetOf(...)`
|
|
53
|
+
* date, so the pull request that moves this window is the one that fails
|
|
54
|
+
* without saying so; and `scripts/verify-version-tag.mjs` requires a dated
|
|
55
|
+
* heading for the tag being released.
|
|
42
56
|
*/
|
|
43
57
|
export declare const PROTOCOL_VERSIONS: readonly ProtocolVersionEntry[];
|
|
44
58
|
/** The newest bridge package. The cloud mails organisations still below it. */
|
|
45
|
-
export declare const LATEST_BRIDGE_VERSION = "4.
|
|
59
|
+
export declare const LATEST_BRIDGE_VERSION = "4.1.0";
|
|
46
60
|
export interface ProtocolStatus {
|
|
47
61
|
status: 'current' | 'deprecated' | 'unsupported';
|
|
48
62
|
/** ISO date, or null for a current or unknown version. */
|
|
@@ -128,6 +142,40 @@ export declare const MAX_PATIENCE_MS = 120000;
|
|
|
128
142
|
* this end bounds what the caller can do to the robot.
|
|
129
143
|
*/
|
|
130
144
|
export declare const MIN_PATIENCE_MS = 1000;
|
|
145
|
+
/**
|
|
146
|
+
* How often a protocol-4 bridge sends a `job_update` heartbeat for every
|
|
147
|
+
* running job — the last known state, whether or not the action itself said
|
|
148
|
+
* anything new. One second: often enough that `JOB_HEARTBEAT_TIMEOUT_MS`
|
|
149
|
+
* can be a small multiple of it and still absorb a missed beat or two, rare
|
|
150
|
+
* enough that it costs nothing next to the datapoint traffic a busy robot
|
|
151
|
+
* already sends.
|
|
152
|
+
*/
|
|
153
|
+
export declare const JOB_HEARTBEAT_INTERVAL_MS = 1000;
|
|
154
|
+
/**
|
|
155
|
+
* How long a protocol-4 job may go without a `job_update` — heartbeat or
|
|
156
|
+
* real progress, either counts — before the cloud settles it `lost` with
|
|
157
|
+
* `bridge_timeout`, once the bridge is connected. Five heartbeats: enough
|
|
158
|
+
* slack for an ordinary scheduling jitter, small next to `patience_ms`
|
|
159
|
+
* because it no longer has to cover the acceptance gap too. `patience_ms`
|
|
160
|
+
* bounds only the time from `invoke` to the *first* update on a protocol-4
|
|
161
|
+
* job; every rearm after that uses this constant instead. A protocol-3
|
|
162
|
+
* bridge sends no heartbeat, so this constant does not apply to it —
|
|
163
|
+
* `patience_ms` keeps bounding the whole running job there, exactly as
|
|
164
|
+
* before.
|
|
165
|
+
*/
|
|
166
|
+
export declare const JOB_HEARTBEAT_TIMEOUT_MS = 5000;
|
|
167
|
+
/**
|
|
168
|
+
* How long a running job survives its robot going offline before the cloud
|
|
169
|
+
* gives up and settles it `lost` with `bridge_disconnected`. Five minutes:
|
|
170
|
+
* long enough that an ordinary Wi-Fi dead zone — the case this constant
|
|
171
|
+
* exists for — never costs a job, since a robot with no safety layer of its
|
|
172
|
+
* own (§ Fleetless is not a safety layer) keeps driving through one and the
|
|
173
|
+
* result the cloud is waiting for is often still coming. A robot connected
|
|
174
|
+
* the whole time never reaches this bound at all: while online, silence is
|
|
175
|
+
* `JOB_HEARTBEAT_TIMEOUT_MS`'s question (protocol 4) or `patience_ms`'s
|
|
176
|
+
* (protocol 3), never this one's.
|
|
177
|
+
*/
|
|
178
|
+
export declare const JOB_OFFLINE_GRACE_MS = 300000;
|
|
131
179
|
/** Re-exported so consumers keep importing wire names from one place. */
|
|
132
180
|
export { slug } from './common.js';
|
|
133
181
|
/**
|
|
@@ -598,10 +646,23 @@ export type BridgeJobUpdate = z.infer<typeof bridgeJobUpdate>;
|
|
|
598
646
|
* left to enumerate, so it is `hello.active_job_ids` that closes that gap.
|
|
599
647
|
* Both paths end in the same place — the cloud publishes `lost` rather than
|
|
600
648
|
* leaving a job reading "running" because nobody contradicted it.
|
|
649
|
+
*
|
|
650
|
+
* **`error` (since protocol 4) is optional and, when present, applies to
|
|
651
|
+
* every job named in `job_ids`.** A vanished action server is discovered
|
|
652
|
+
* once, by the bridge's own liveness check on that one goal, so a frame
|
|
653
|
+
* naming several jobs at once — plausible if several goals shared the same
|
|
654
|
+
* server — always shares the same cause. Absent means today's behaviour:
|
|
655
|
+
* the cloud settles the job `lost` with no specific code, the same as a
|
|
656
|
+
* protocol-3 bridge's frame, which carries no `error` at all and still
|
|
657
|
+
* parses under this schema unchanged.
|
|
601
658
|
*/
|
|
602
659
|
export declare const bridgeJobLost: z.ZodObject<{
|
|
603
660
|
type: z.ZodLiteral<"job_lost">;
|
|
604
661
|
job_ids: z.ZodArray<z.ZodUUID>;
|
|
662
|
+
error: z.ZodOptional<z.ZodObject<{
|
|
663
|
+
code: z.ZodString;
|
|
664
|
+
message: z.ZodString;
|
|
665
|
+
}, z.core.$strip>>;
|
|
605
666
|
}, z.core.$strip>;
|
|
606
667
|
export type BridgeJobLost = z.infer<typeof bridgeJobLost>;
|
|
607
668
|
/** Cloud asks for a fresh ROS graph; `request_id` correlates the answer. */
|
package/dist/protocol.js
CHANGED
|
@@ -7,7 +7,7 @@ import { rosGraph, typeDefinition } from './introspection.js';
|
|
|
7
7
|
import { jobState } from './jobs.js';
|
|
8
8
|
import { rosTypeName } from './common.js';
|
|
9
9
|
/**
|
|
10
|
-
* Bridge <-> cloud protocol, version
|
|
10
|
+
* Bridge <-> cloud protocol, version 4.
|
|
11
11
|
*
|
|
12
12
|
* The version is exchanged in the hello handshake. Since 2026-09 the cloud
|
|
13
13
|
* serves a **window** of versions, not one: every entry of
|
|
@@ -17,6 +17,18 @@ import { rosTypeName } from './common.js';
|
|
|
17
17
|
* which names the window and reaches the robot's detail view as
|
|
18
18
|
* `last_hello_error`.
|
|
19
19
|
*
|
|
20
|
+
* **4 (2026-09-28):** the bridge sends a `job_update` heartbeat at
|
|
21
|
+
* `JOB_HEARTBEAT_INTERVAL_MS` for every running job, whether or not the
|
|
22
|
+
* action said anything new, and reports a vanished action server with
|
|
23
|
+
* `job_lost`'s new optional `error`, `action_server_lost`. The cloud bounds
|
|
24
|
+
* a protocol-4 job's silence by the heartbeat (`JOB_HEARTBEAT_TIMEOUT_MS`)
|
|
25
|
+
* once it has heard from the job at all; `patience_ms` still bounds
|
|
26
|
+
* acceptance, the same as before. Offline tolerance is
|
|
27
|
+
* `JOB_OFFLINE_GRACE_MS` (five minutes), up from the informal one minute a
|
|
28
|
+
* protocol-3 bridge got. A protocol-3 bridge sends no heartbeat and keeps
|
|
29
|
+
* today's behaviour exactly: `patience_ms` alone bounds the whole running
|
|
30
|
+
* job, silence included.
|
|
31
|
+
*
|
|
20
32
|
* **3 (2026-09-22):** the ping carries `latency_ms` and `lag_ms`, the bridge
|
|
21
33
|
* sends `link_mode`, `bridge_state` gains `low_bandwidth`, and the
|
|
22
34
|
* `bridge_pressure` datapoint is gone. A protocol-2 bridge is served until
|
|
@@ -29,22 +41,25 @@ import { rosTypeName } from './common.js';
|
|
|
29
41
|
* **2 (2026-08-21):** `config_applied.errors` entries gained `kind` and `code`
|
|
30
42
|
* beside `message`.
|
|
31
43
|
*/
|
|
32
|
-
export const PROTOCOL_VERSION =
|
|
44
|
+
export const PROTOCOL_VERSION = 4;
|
|
33
45
|
/** Days between a version's deprecation and its sunset. */
|
|
34
46
|
export const PROTOCOL_SUNSET_DAYS = 90;
|
|
35
47
|
/**
|
|
36
48
|
* Every protocol version the cloud has served, oldest first. A test keeps
|
|
37
49
|
* exactly one entry current and equal to `PROTOCOL_VERSION`; `test/changelog.test.ts`
|
|
38
|
-
* requires
|
|
39
|
-
*
|
|
40
|
-
*
|
|
50
|
+
* requires some CHANGELOG section — `[Unreleased]` or a dated one — to name
|
|
51
|
+
* the newest `bridge_from` together with the previous entry's `sunsetOf(...)`
|
|
52
|
+
* date, so the pull request that moves this window is the one that fails
|
|
53
|
+
* without saying so; and `scripts/verify-version-tag.mjs` requires a dated
|
|
54
|
+
* heading for the tag being released.
|
|
41
55
|
*/
|
|
42
56
|
export const PROTOCOL_VERSIONS = [
|
|
43
57
|
{ version: 2, bridge_from: '3.0.0', deprecated_at: '2026-09-22' },
|
|
44
|
-
{ version: 3, bridge_from: '4.0.0', deprecated_at:
|
|
58
|
+
{ version: 3, bridge_from: '4.0.0', deprecated_at: '2026-09-28' },
|
|
59
|
+
{ version: 4, bridge_from: '4.1.0', deprecated_at: null },
|
|
45
60
|
];
|
|
46
61
|
/** The newest bridge package. The cloud mails organisations still below it. */
|
|
47
|
-
export const LATEST_BRIDGE_VERSION = '4.
|
|
62
|
+
export const LATEST_BRIDGE_VERSION = '4.1.0';
|
|
48
63
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
49
64
|
function isoDate(date) {
|
|
50
65
|
return date.toISOString().slice(0, 10);
|
|
@@ -146,6 +161,40 @@ export const MAX_PATIENCE_MS = 120_000;
|
|
|
146
161
|
* this end bounds what the caller can do to the robot.
|
|
147
162
|
*/
|
|
148
163
|
export const MIN_PATIENCE_MS = 1_000;
|
|
164
|
+
/**
|
|
165
|
+
* How often a protocol-4 bridge sends a `job_update` heartbeat for every
|
|
166
|
+
* running job — the last known state, whether or not the action itself said
|
|
167
|
+
* anything new. One second: often enough that `JOB_HEARTBEAT_TIMEOUT_MS`
|
|
168
|
+
* can be a small multiple of it and still absorb a missed beat or two, rare
|
|
169
|
+
* enough that it costs nothing next to the datapoint traffic a busy robot
|
|
170
|
+
* already sends.
|
|
171
|
+
*/
|
|
172
|
+
export const JOB_HEARTBEAT_INTERVAL_MS = 1_000;
|
|
173
|
+
/**
|
|
174
|
+
* How long a protocol-4 job may go without a `job_update` — heartbeat or
|
|
175
|
+
* real progress, either counts — before the cloud settles it `lost` with
|
|
176
|
+
* `bridge_timeout`, once the bridge is connected. Five heartbeats: enough
|
|
177
|
+
* slack for an ordinary scheduling jitter, small next to `patience_ms`
|
|
178
|
+
* because it no longer has to cover the acceptance gap too. `patience_ms`
|
|
179
|
+
* bounds only the time from `invoke` to the *first* update on a protocol-4
|
|
180
|
+
* job; every rearm after that uses this constant instead. A protocol-3
|
|
181
|
+
* bridge sends no heartbeat, so this constant does not apply to it —
|
|
182
|
+
* `patience_ms` keeps bounding the whole running job there, exactly as
|
|
183
|
+
* before.
|
|
184
|
+
*/
|
|
185
|
+
export const JOB_HEARTBEAT_TIMEOUT_MS = 5_000;
|
|
186
|
+
/**
|
|
187
|
+
* How long a running job survives its robot going offline before the cloud
|
|
188
|
+
* gives up and settles it `lost` with `bridge_disconnected`. Five minutes:
|
|
189
|
+
* long enough that an ordinary Wi-Fi dead zone — the case this constant
|
|
190
|
+
* exists for — never costs a job, since a robot with no safety layer of its
|
|
191
|
+
* own (§ Fleetless is not a safety layer) keeps driving through one and the
|
|
192
|
+
* result the cloud is waiting for is often still coming. A robot connected
|
|
193
|
+
* the whole time never reaches this bound at all: while online, silence is
|
|
194
|
+
* `JOB_HEARTBEAT_TIMEOUT_MS`'s question (protocol 4) or `patience_ms`'s
|
|
195
|
+
* (protocol 3), never this one's.
|
|
196
|
+
*/
|
|
197
|
+
export const JOB_OFFLINE_GRACE_MS = 300_000;
|
|
149
198
|
/** Re-exported so consumers keep importing wire names from one place. */
|
|
150
199
|
export { slug } from './common.js';
|
|
151
200
|
/**
|
|
@@ -440,10 +489,20 @@ export const bridgeJobUpdate = z.object({
|
|
|
440
489
|
* left to enumerate, so it is `hello.active_job_ids` that closes that gap.
|
|
441
490
|
* Both paths end in the same place — the cloud publishes `lost` rather than
|
|
442
491
|
* leaving a job reading "running" because nobody contradicted it.
|
|
492
|
+
*
|
|
493
|
+
* **`error` (since protocol 4) is optional and, when present, applies to
|
|
494
|
+
* every job named in `job_ids`.** A vanished action server is discovered
|
|
495
|
+
* once, by the bridge's own liveness check on that one goal, so a frame
|
|
496
|
+
* naming several jobs at once — plausible if several goals shared the same
|
|
497
|
+
* server — always shares the same cause. Absent means today's behaviour:
|
|
498
|
+
* the cloud settles the job `lost` with no specific code, the same as a
|
|
499
|
+
* protocol-3 bridge's frame, which carries no `error` at all and still
|
|
500
|
+
* parses under this schema unchanged.
|
|
443
501
|
*/
|
|
444
502
|
export const bridgeJobLost = z.object({
|
|
445
503
|
type: z.literal('job_lost'),
|
|
446
504
|
job_ids: z.array(z.uuid()),
|
|
505
|
+
error: z.object({ code: z.string().min(1), message: z.string().min(1) }).optional(),
|
|
447
506
|
});
|
|
448
507
|
/** Cloud asks for a fresh ROS graph; `request_id` correlates the answer. */
|
|
449
508
|
export const cloudIntrospectRequest = z.object({
|
package/dist/realtime.d.ts
CHANGED
|
@@ -256,8 +256,8 @@ export declare const liveSessionEndReason: z.ZodEnum<{
|
|
|
256
256
|
unknown: "unknown";
|
|
257
257
|
publish_failed: "publish_failed";
|
|
258
258
|
robot_offline: "robot_offline";
|
|
259
|
-
released_by_peer: "released_by_peer";
|
|
260
259
|
config_changed: "config_changed";
|
|
260
|
+
released_by_peer: "released_by_peer";
|
|
261
261
|
revoked: "revoked";
|
|
262
262
|
expired: "expired";
|
|
263
263
|
robot_deleted: "robot_deleted";
|
|
@@ -287,8 +287,8 @@ export declare const liveSessionEvent: z.ZodObject<{
|
|
|
287
287
|
unknown: "unknown";
|
|
288
288
|
publish_failed: "publish_failed";
|
|
289
289
|
robot_offline: "robot_offline";
|
|
290
|
-
released_by_peer: "released_by_peer";
|
|
291
290
|
config_changed: "config_changed";
|
|
291
|
+
released_by_peer: "released_by_peer";
|
|
292
292
|
revoked: "revoked";
|
|
293
293
|
expired: "expired";
|
|
294
294
|
robot_deleted: "robot_deleted";
|
package/dist/rest.d.ts
CHANGED
|
@@ -1440,6 +1440,8 @@ export type HistoryResponse = z.infer<typeof historyResponse>;
|
|
|
1440
1440
|
* |---|---|---|
|
|
1441
1441
|
* | `DELETE /api/robots/:id` | — | `204`. `?force=true` to proceed while a live session is open; without it, `409 robot_in_use` |
|
|
1442
1442
|
* | `GET /api/robots/:id/deletion-preview` | — | `robotDeletionSummary` — the same shape the audit event carries |
|
|
1443
|
+
* | `DELETE /api/apps/:id` | — | `204`. No `force` parameter — an app has no open-session hazard to force past, so the preview is the guard |
|
|
1444
|
+
* | `GET /api/apps/:id/deletion-preview` | — | `appDeletionSummary` — the same shape the audit event carries |
|
|
1443
1445
|
* | `GET /api/org/health` | — | `resourceHealthListResponse`; `?robot_id=` narrows it to one robot |
|
|
1444
1446
|
*
|
|
1445
1447
|
* Plus `resourceHealthEvent`, pushed on the **developer** realtime socket
|
package/dist/rest.js
CHANGED
|
@@ -1228,6 +1228,8 @@ export const historyResponse = z.union([historySamplesResponse, historyBucketsRe
|
|
|
1228
1228
|
* |---|---|---|
|
|
1229
1229
|
* | `DELETE /api/robots/:id` | — | `204`. `?force=true` to proceed while a live session is open; without it, `409 robot_in_use` |
|
|
1230
1230
|
* | `GET /api/robots/:id/deletion-preview` | — | `robotDeletionSummary` — the same shape the audit event carries |
|
|
1231
|
+
* | `DELETE /api/apps/:id` | — | `204`. No `force` parameter — an app has no open-session hazard to force past, so the preview is the guard |
|
|
1232
|
+
* | `GET /api/apps/:id/deletion-preview` | — | `appDeletionSummary` — the same shape the audit event carries |
|
|
1231
1233
|
* | `GET /api/org/health` | — | `resourceHealthListResponse`; `?robot_id=` narrows it to one robot |
|
|
1232
1234
|
*
|
|
1233
1235
|
* Plus `resourceHealthEvent`, pushed on the **developer** realtime socket
|
package/dist/routes.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
import { appListResponse, createAppRequest, createServerKeyResponse, app as appSchema, role, roleListResponse, rolePermissions, serverKeyListResponse, updateAppRequest, } from './apps.js';
|
|
2
|
+
import { appListResponse, appDeletionSummary, createAppRequest, createServerKeyResponse, app as appSchema, role, roleListResponse, rolePermissions, serverKeyListResponse, updateAppRequest, } from './apps.js';
|
|
3
3
|
import { alertListResponse, orgAlertsQuery, orgFiringAlertsResponse } from './alerts.js';
|
|
4
4
|
import { asset, assetListResponse, assetsClearResponse, assetSyncRequest, assetSyncResponse, assetSyncStatus, missingAssetQuery } from './assets.js';
|
|
5
5
|
import { auditListResponse, auditQuery } from './audit.js';
|
|
6
6
|
import { CLIENT_OIDC_CALLBACK_PATH, clientAcceptInvitationRequest, clientIdentity, clientLoginRequest, clientLogoutRequest, clientMcpInteraction, clientMcpInteractionDecisionResponse, clientOidcCallbackQuery, clientOidcExchangeRequest, clientOidcStartQuery, clientPasswordResetConfirmRequest, clientPasswordResetRequest, clientProviderListQuery, clientProviderListResponse, clientRefreshRequest, clientRegisterRequest, clientResendVerificationRequest, clientVerifyEmailRequest, mcpConsentGrantListResponse, } from './client-auth.js';
|
|
7
7
|
import { clientRobotListResponse } from './client-robots.js';
|
|
8
|
-
import { appAuthConfig, appInvitation, appInvitationListResponse, appMailTemplate, appMailTemplateListResponse, appOidcProvider, appOidcProviderListResponse, appUser, appUserListResponse, createAppInvitationRequest, createAppOidcProviderRequest, createAppUserRequest, mailOutcome, mailTemplatePreviewRequest, mailTemplatePreviewResponse, patchAppOidcProviderRequest, patchAppUserRequest,
|
|
8
|
+
import { appAuthConfig, appInvitation, appInvitationListResponse, appMailTemplate, appMailTemplateListResponse, appOidcProvider, appOidcProviderListResponse, appUser, appUserListResponse, createAppInvitationRequest, createAppOidcProviderRequest, createAppUserRequest, mailOutcome, mailTemplatePreviewRequest, mailTemplatePreviewResponse, patchAppOidcProviderRequest, patchAppUserRequest, putAppAuthMcpRequest, putAppAuthRegistrationRequest, putAppAuthUrlsRequest, putAppMailTemplateRequest, } from './app-users.js';
|
|
9
9
|
import { acceptTeamInviteRequest, authMeResponse, createTeamInviteRequest, fleetlessUser, fleetlessUserListResponse, passwordChangeRequest, passwordResetConfirm, passwordResetRequest, patchAuthMeRequest, patchFleetlessUserRequest, patchOrgRequest, patchOrgResponse, pendingTeamInviteListResponse, refreshRequest, sessionTokens, signUpRequest, signUpResponse, teamInvite, tierChangeRequest, waitlistRequest, } from './identity.js';
|
|
10
10
|
import { jobRunListResponse, jobRunQuery, jobRunSummary, jobRunSummaryQuery } from './jobs.js';
|
|
11
11
|
import { MCP_APP_PATHS, mcpRobotDatasheet, mcpRolePreviewResponse } from './mcp.js';
|
|
@@ -295,6 +295,32 @@ export const ROUTES = [
|
|
|
295
295
|
notes: 'A `default_role_id` naming a role of another app is refused: it is the one cross-app authorization check this shape can carry. ' +
|
|
296
296
|
'Changing the robot set closes every live subscription the app\'s users hold, since a grant may no longer name a reachable robot.',
|
|
297
297
|
},
|
|
298
|
+
{
|
|
299
|
+
method: 'GET', path: '/api/apps/:id/deletion-preview', section: 'apps',
|
|
300
|
+
summary: 'Reports what deleting the app would destroy, without destroying it.',
|
|
301
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 200,
|
|
302
|
+
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }],
|
|
303
|
+
query: null, request: null, response: appDeletionSummary,
|
|
304
|
+
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'not_found'], transport: 'http',
|
|
305
|
+
notes: 'The same shape the delete\'s own audit event carries, computed by the same function on purpose: the confirmation dialog and the eventual ' +
|
|
306
|
+
'receipt agree by construction, and any difference between them is real drift rather than two estimates that quietly disagree. \n\n' +
|
|
307
|
+
'**No `force` parameter, unlike the robot pair this is modelled on.** A robot\'s open live session is a single nameable state whose ' +
|
|
308
|
+
'interruption is its own hazard, which is why that route makes the caller pass `force` explicitly. An app has no equivalent state to ' +
|
|
309
|
+
'force past, and inventing one would be a guess wearing a guard\'s clothes — this preview is the guard.',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
method: 'DELETE', path: '/api/apps/:id', section: 'apps',
|
|
313
|
+
summary: 'Deletes an app and everything it produced.',
|
|
314
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: true, status: 204,
|
|
315
|
+
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }],
|
|
316
|
+
query: null, request: null, response: null,
|
|
317
|
+
errors: [...DEVELOPER_GUARD, 'tier_required', 'invalid_uuid', 'not_found'], transport: 'http',
|
|
318
|
+
notes: 'Owner tier, and the gate runs **after** the org-scoped lookup: a developer-tier admin therefore sees the same `404` a stranger would ' +
|
|
319
|
+
'for an app outside their org, rather than a tier refusal that confirms the id exists. A full cascade — its users, roles, server keys, ' +
|
|
320
|
+
'invitations, OIDC provider configuration and mail templates all go, recorded once as `app.deleted` carrying an `appDeletionSummary`. ' +
|
|
321
|
+
'Its robots are untouched: they belong to the org, not to the app. \n\n**No `force` parameter** — see ' +
|
|
322
|
+
'`GET /api/apps/:id/deletion-preview`.',
|
|
323
|
+
},
|
|
298
324
|
{
|
|
299
325
|
method: 'POST', path: '/api/apps/:id/roles', section: 'apps',
|
|
300
326
|
summary: 'Creates a custom role on the app.',
|
|
@@ -681,22 +707,55 @@ export const ROUTES = [
|
|
|
681
707
|
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'not_found'], transport: 'http',
|
|
682
708
|
notes: 'One row per app, created with the app and never absent — an app that has configured nothing reads back the defaults rather than a ' +
|
|
683
709
|
'`404`. `oidc_callback_url` is in the answer and not in the request: it is minted by the cloud from its own public base URL, is the same ' +
|
|
684
|
-
'for every app and every provider, and is the value a developer registers at their identity provider.'
|
|
710
|
+
'for every app and every provider, and is the value a developer registers at their identity provider. It stays read-only on every slice ' +
|
|
711
|
+
'write below for a second reason: a writable callback URL would let a caller point the return leg of an OIDC sign-in, which carries an ' +
|
|
712
|
+
'authorization code, at a host they own. `updated_at` is read-only for a duller one: the server stamps it on every write, and a ' +
|
|
713
|
+
'client-supplied value would be a lie about when the row last changed.',
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
method: 'PUT', path: '/api/apps/:id/auth-config/registration', section: 'apps',
|
|
717
|
+
summary: 'Replaces who may self-register, and from where.',
|
|
718
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 200,
|
|
719
|
+
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }],
|
|
720
|
+
query: null, request: putAppAuthRegistrationRequest, response: appAuthConfig,
|
|
721
|
+
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'validation_error', 'not_found'], transport: 'http',
|
|
722
|
+
notes: '**A replace, not a merge, and `.strict()`**: `self_registration`, `allowed_domains` and `allowed_origins` all arrive or the write is ' +
|
|
723
|
+
'refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and ' +
|
|
724
|
+
'`updated_at` are the server\'s, refused in this body as in every slice\'s — see `GET`\'s notes for why. ' +
|
|
725
|
+
'\n\n`400 validation_error` is where the two field rules land: an entry in `allowed_domains` must be lowercase, since a capitalised one ' +
|
|
726
|
+
'can never match a lowercased address, and an entry in `allowed_origins` must be a bare scheme-host-port with no path, since a browser ' +
|
|
727
|
+
'sends nothing longer in its `Origin` header. Each refuses at configuration time rather than failing silently later. ' +
|
|
728
|
+
'\n\nThe merge is server-side against the stored row, so this write never disturbs the urls or mcp slice.',
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
method: 'PUT', path: '/api/apps/:id/auth-config/urls', section: 'apps',
|
|
732
|
+
summary: "Replaces the three pages Fleetless's mails point at.",
|
|
733
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 200,
|
|
734
|
+
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }],
|
|
735
|
+
query: null, request: putAppAuthUrlsRequest, response: appAuthConfig,
|
|
736
|
+
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'validation_error', 'not_found'], transport: 'http',
|
|
737
|
+
notes: '**A replace, not a merge, and `.strict()`**: `invite_url`, `verify_url` and `reset_url` all arrive or the write is refused, so a ' +
|
|
738
|
+
'client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are ' +
|
|
739
|
+
'the server\'s, refused in this body as in every slice\'s — see `GET`\'s notes for why. ' +
|
|
740
|
+
'\n\n`400 validation_error` is where the field rule lands: a URL template must be https (or `http` on `localhost`) and carry its ' +
|
|
741
|
+
'placeholder exactly once — a second occurrence leaves one literal in a mailed link, refused here rather than failing silently once ' +
|
|
742
|
+
'the mail is sent. ' +
|
|
743
|
+
'\n\nThe merge is server-side against the stored row, so this write never disturbs the registration or mcp slice.',
|
|
685
744
|
},
|
|
686
745
|
{
|
|
687
|
-
method: 'PUT', path: '/api/apps/:id/auth-config', section: 'apps',
|
|
688
|
-
summary:
|
|
746
|
+
method: 'PUT', path: '/api/apps/:id/auth-config/mcp', section: 'apps',
|
|
747
|
+
summary: 'Replaces the MCP switch and its login URL together.',
|
|
689
748
|
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: false, status: 200,
|
|
690
749
|
params: [{ name: 'id', description: 'The app\'s uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.' }],
|
|
691
|
-
query: null, request:
|
|
750
|
+
query: null, request: putAppAuthMcpRequest, response: appAuthConfig,
|
|
692
751
|
errors: [...DEVELOPER_GUARD, 'invalid_uuid', 'validation_error', 'not_found'], transport: 'http',
|
|
693
|
-
notes: '**A replace, not a merge, and `.strict()`**:
|
|
694
|
-
'cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are
|
|
695
|
-
'
|
|
696
|
-
'\n\n`
|
|
697
|
-
'
|
|
698
|
-
'
|
|
699
|
-
'
|
|
752
|
+
notes: '**A replace, not a merge, and `.strict()`**: `mcp_enabled` and `mcp_login_url` both arrive or the write is refused, so a client built ' +
|
|
753
|
+
'against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server\'s, ' +
|
|
754
|
+
'refused in this body as in every slice\'s — see `GET`\'s notes for why. ' +
|
|
755
|
+
'\n\n`mcp_login_url` answers to the same rule as the `urls` slice\'s three templates — https (or `http` on `localhost`), its placeholder ' +
|
|
756
|
+
'exactly once — refused as `400 validation_error` rather than left to fail mid-OAuth, in a client\'s browser where no console screen ' +
|
|
757
|
+
'is watching. ' +
|
|
758
|
+
'\n\nThe merge is server-side against the stored row, so this write never disturbs the registration or urls slice.',
|
|
700
759
|
},
|
|
701
760
|
{
|
|
702
761
|
method: 'GET', path: '/api/apps/:id/mail-templates', section: 'apps',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetless/contracts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-next.1",
|
|
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",
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"type": "object",
|
|
4
|
-
"properties": {
|
|
5
|
-
"self_registration": {
|
|
6
|
-
"type": "boolean",
|
|
7
|
-
"description": "Whether a stranger may create an account in this app. Off refuses `POST /api/client/register` with `403 registration_closed`, and refuses an unknown identity at an OIDC callback with the same reasoning — one switch for one decision, whichever door the person arrives at."
|
|
8
|
-
},
|
|
9
|
-
"allowed_domains": {
|
|
10
|
-
"maxItems": 50,
|
|
11
|
-
"type": "array",
|
|
12
|
-
"items": {
|
|
13
|
-
"type": "string",
|
|
14
|
-
"minLength": 1,
|
|
15
|
-
"maxLength": 253,
|
|
16
|
-
"pattern": "^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$"
|
|
17
|
-
},
|
|
18
|
-
"description": "The email domains self-registration accepts, lowercase. An empty list means no domain restriction, not \"nobody\" — the switch above is what closes the door. **An invitation always bypasses this**, by password and through a provider alike."
|
|
19
|
-
},
|
|
20
|
-
"allowed_origins": {
|
|
21
|
-
"maxItems": 20,
|
|
22
|
-
"type": "array",
|
|
23
|
-
"items": {
|
|
24
|
-
"type": "string",
|
|
25
|
-
"maxLength": 200
|
|
26
|
-
},
|
|
27
|
-
"description": "The origins the client auth API answers CORS for, and the only origins an OIDC `redirect_uri` may name. Bare origins: scheme, host and port, with no path — a browser sends nothing longer, so an entry carrying one could never match."
|
|
28
|
-
},
|
|
29
|
-
"mcp_enabled": {
|
|
30
|
-
"type": "boolean",
|
|
31
|
-
"description": "Whether this app serves an MCP endpoint at `/mcp/<identifier>`. Off refuses the whole OAuth surface for the app, not merely the tool calls, and is re-read on every request rather than cached off a token."
|
|
32
|
-
},
|
|
33
|
-
"invite_url": {
|
|
34
|
-
"anyOf": [
|
|
35
|
-
{
|
|
36
|
-
"type": "string",
|
|
37
|
-
"maxLength": 500
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"type": "null"
|
|
41
|
-
}
|
|
42
|
-
],
|
|
43
|
-
"description": "The page in the developer's app that accepts an invitation, with `{token}` where the token goes. `null` when unconfigured, and then an invitation still issues but `send_mail` is refused with `409 target_state_conflict` — there would be nowhere for the link to point."
|
|
44
|
-
},
|
|
45
|
-
"verify_url": {
|
|
46
|
-
"anyOf": [
|
|
47
|
-
{
|
|
48
|
-
"type": "string",
|
|
49
|
-
"maxLength": 500
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"type": "null"
|
|
53
|
-
}
|
|
54
|
-
],
|
|
55
|
-
"description": "The page that confirms a new address, with `{token}` where the token goes. Self-registration needs it: without a page to send people to, a registration would leave an account nobody can activate."
|
|
56
|
-
},
|
|
57
|
-
"reset_url": {
|
|
58
|
-
"anyOf": [
|
|
59
|
-
{
|
|
60
|
-
"type": "string",
|
|
61
|
-
"maxLength": 500
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
"type": "null"
|
|
65
|
-
}
|
|
66
|
-
],
|
|
67
|
-
"description": "The page that takes a new password, with `{token}` where the token goes."
|
|
68
|
-
},
|
|
69
|
-
"mcp_login_url": {
|
|
70
|
-
"anyOf": [
|
|
71
|
-
{
|
|
72
|
-
"type": "string",
|
|
73
|
-
"maxLength": 500
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
"type": "null"
|
|
77
|
-
}
|
|
78
|
-
],
|
|
79
|
-
"description": "The page an MCP authorization redirects to, with `{interaction}` where the interaction id goes. Not a token: the id names a pending request the server already holds, and the app authenticates the user itself before approving it."
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
"required": [
|
|
83
|
-
"self_registration",
|
|
84
|
-
"allowed_domains",
|
|
85
|
-
"allowed_origins",
|
|
86
|
-
"mcp_enabled",
|
|
87
|
-
"invite_url",
|
|
88
|
-
"verify_url",
|
|
89
|
-
"reset_url",
|
|
90
|
-
"mcp_login_url"
|
|
91
|
-
],
|
|
92
|
-
"additionalProperties": false
|
|
93
|
-
}
|