@fleetless/contracts 1.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 +26 -0
- package/LICENSE +202 -0
- package/NOTICE +17 -0
- package/README.md +88 -0
- package/artifacts/constants.json +24 -0
- package/artifacts/openapi.json +17219 -0
- package/artifacts/routes.json +4605 -0
- package/artifacts/schema/accept-team-invite-request.schema.json +22 -0
- package/artifacts/schema/action-config.schema.json +198 -0
- package/artifacts/schema/alert-list-response.schema.json +172 -0
- package/artifacts/schema/api-error.schema.json +20 -0
- package/artifacts/schema/app-auth-config.schema.json +106 -0
- package/artifacts/schema/app-invitation-list-response.schema.json +57 -0
- package/artifacts/schema/app-invitation.schema.json +69 -0
- package/artifacts/schema/app-list-response.schema.json +82 -0
- package/artifacts/schema/app-mail-template-list-response.schema.json +68 -0
- package/artifacts/schema/app-mail-template.schema.json +54 -0
- package/artifacts/schema/app-oidc-provider-list-response.schema.json +93 -0
- package/artifacts/schema/app-oidc-provider.schema.json +80 -0
- package/artifacts/schema/app-user-list-response.schema.json +111 -0
- package/artifacts/schema/app-user.schema.json +98 -0
- package/artifacts/schema/app.schema.json +69 -0
- package/artifacts/schema/apply-error.schema.json +41 -0
- package/artifacts/schema/asset-list-response.schema.json +288 -0
- package/artifacts/schema/asset-sync-request.schema.json +17 -0
- package/artifacts/schema/asset-sync-response.schema.json +16 -0
- package/artifacts/schema/asset-sync-status.schema.json +136 -0
- package/artifacts/schema/asset.schema.json +68 -0
- package/artifacts/schema/audit-actor.schema.json +32 -0
- package/artifacts/schema/audit-event.schema.json +119 -0
- package/artifacts/schema/audit-list-response.schema.json +144 -0
- package/artifacts/schema/audit-query.schema.json +79 -0
- package/artifacts/schema/auth-error.schema.json +23 -0
- package/artifacts/schema/auth-me-response.schema.json +99 -0
- package/artifacts/schema/auth-ok.schema.json +115 -0
- package/artifacts/schema/authorization-server-metadata.schema.json +80 -0
- package/artifacts/schema/bridge-asset-progress.schema.json +99 -0
- package/artifacts/schema/bridge-assets-available.schema.json +25 -0
- package/artifacts/schema/bridge-camera-state.schema.json +78 -0
- package/artifacts/schema/bridge-config-applied.schema.json +67 -0
- package/artifacts/schema/bridge-hello.schema.json +65 -0
- package/artifacts/schema/bridge-introspect.schema.json +114 -0
- package/artifacts/schema/bridge-job-lost.schema.json +22 -0
- package/artifacts/schema/bridge-job-update.schema.json +100 -0
- package/artifacts/schema/bridge-pong.schema.json +19 -0
- package/artifacts/schema/bridge-pressure.schema.json +292 -0
- package/artifacts/schema/bridge-state.schema.json +24 -0
- package/artifacts/schema/bridge-type-definitions.schema.json +169 -0
- package/artifacts/schema/busy-details.schema.json +115 -0
- package/artifacts/schema/camera-descriptor.schema.json +45 -0
- package/artifacts/schema/camera-list-response.schema.json +58 -0
- package/artifacts/schema/camera-source.schema.json +240 -0
- package/artifacts/schema/cancel-request.schema.json +20 -0
- package/artifacts/schema/client-accept-invitation-request.schema.json +35 -0
- package/artifacts/schema/client-auth.schema.json +18 -0
- package/artifacts/schema/client-cancel.schema.json +45 -0
- package/artifacts/schema/client-identity.schema.json +103 -0
- package/artifacts/schema/client-invoke.schema.json +45 -0
- package/artifacts/schema/client-login-request.schema.json +29 -0
- package/artifacts/schema/client-logout-request.schema.json +14 -0
- package/artifacts/schema/client-mcp-interaction-decision-response.schema.json +15 -0
- package/artifacts/schema/client-mcp-interaction.schema.json +59 -0
- package/artifacts/schema/client-oidc-callback-query.schema.json +28 -0
- package/artifacts/schema/client-oidc-exchange-request.schema.json +21 -0
- package/artifacts/schema/client-oidc-start-query.schema.json +36 -0
- package/artifacts/schema/client-password-reset-confirm-request.schema.json +22 -0
- package/artifacts/schema/client-password-reset-request.schema.json +24 -0
- package/artifacts/schema/client-provider-list-query.schema.json +17 -0
- package/artifacts/schema/client-provider-list-response.schema.json +34 -0
- package/artifacts/schema/client-publish.schema.json +40 -0
- package/artifacts/schema/client-refresh-request.schema.json +14 -0
- package/artifacts/schema/client-register-request.schema.json +44 -0
- package/artifacts/schema/client-resend-verification-request.schema.json +24 -0
- package/artifacts/schema/client-subscribe.schema.json +43 -0
- package/artifacts/schema/client-unsubscribe.schema.json +26 -0
- package/artifacts/schema/client-verify-email-request.schema.json +15 -0
- package/artifacts/schema/cloud-asset-request.schema.json +37 -0
- package/artifacts/schema/cloud-camera-start.schema.json +41 -0
- package/artifacts/schema/cloud-camera-stop.schema.json +26 -0
- package/artifacts/schema/cloud-cancel.schema.json +33 -0
- package/artifacts/schema/cloud-config.schema.json +1635 -0
- package/artifacts/schema/cloud-hello-error.schema.json +23 -0
- package/artifacts/schema/cloud-hello-ok.schema.json +19 -0
- package/artifacts/schema/cloud-introspect-request.schema.json +19 -0
- package/artifacts/schema/cloud-invoke.schema.json +40 -0
- package/artifacts/schema/cloud-ping.schema.json +19 -0
- package/artifacts/schema/cloud-publish.schema.json +28 -0
- package/artifacts/schema/cloud-type-request.schema.json +30 -0
- package/artifacts/schema/command-result.schema.json +175 -0
- package/artifacts/schema/config-draft-response.schema.json +1695 -0
- package/artifacts/schema/config-state.schema.json +124 -0
- package/artifacts/schema/config-version-response.schema.json +1641 -0
- package/artifacts/schema/config-versions-response.schema.json +33 -0
- package/artifacts/schema/create-app-invitation-request.schema.json +40 -0
- package/artifacts/schema/create-app-oidc-provider-request.schema.json +70 -0
- package/artifacts/schema/create-app-request.schema.json +30 -0
- package/artifacts/schema/create-app-user-request.schema.json +42 -0
- package/artifacts/schema/create-robot-request.schema.json +14 -0
- package/artifacts/schema/create-robot-response.schema.json +44 -0
- package/artifacts/schema/create-server-key-response.schema.json +65 -0
- package/artifacts/schema/create-team-invite-request.schema.json +43 -0
- package/artifacts/schema/datapoint-alert-row.schema.json +160 -0
- package/artifacts/schema/datapoint-config.schema.json +366 -0
- package/artifacts/schema/datapoint-display.schema.json +31 -0
- package/artifacts/schema/datapoint-event.schema.json +34 -0
- package/artifacts/schema/datapoint-frame.schema.json +28 -0
- package/artifacts/schema/datapoint-list-response.schema.json +61 -0
- package/artifacts/schema/datapoint-value.schema.json +28 -0
- package/artifacts/schema/developer-login-request.schema.json +19 -0
- package/artifacts/schema/dynamic-client-registration-request.schema.json +60 -0
- package/artifacts/schema/dynamic-client-registration-response.schema.json +68 -0
- package/artifacts/schema/error-frame.schema.json +23 -0
- package/artifacts/schema/exposure-counts.schema.json +39 -0
- package/artifacts/schema/exposure-list-response.schema.json +43 -0
- package/artifacts/schema/fetch-types-request.schema.json +19 -0
- package/artifacts/schema/fetch-types-response.schema.json +163 -0
- package/artifacts/schema/fleetless-user-list-response.schema.json +73 -0
- package/artifacts/schema/fleetless-user.schema.json +60 -0
- package/artifacts/schema/history-buckets-response.schema.json +79 -0
- package/artifacts/schema/history-query.schema.json +58 -0
- package/artifacts/schema/history-response.schema.json +150 -0
- package/artifacts/schema/history-samples-response.schema.json +68 -0
- package/artifacts/schema/introspection-response.schema.json +118 -0
- package/artifacts/schema/invoke-or-service-response.schema.json +141 -0
- package/artifacts/schema/invoke-request.schema.json +23 -0
- package/artifacts/schema/invoke-response.schema.json +125 -0
- package/artifacts/schema/job-actor.schema.json +34 -0
- package/artifacts/schema/job-event.schema.json +158 -0
- package/artifacts/schema/job-response.schema.json +123 -0
- package/artifacts/schema/job-run-list-response.schema.json +222 -0
- package/artifacts/schema/job-run-query.schema.json +95 -0
- package/artifacts/schema/job-run-summary-query.schema.json +23 -0
- package/artifacts/schema/job-run-summary.schema.json +33 -0
- package/artifacts/schema/job-run.schema.json +195 -0
- package/artifacts/schema/job-state.schema.json +11 -0
- package/artifacts/schema/job.schema.json +106 -0
- package/artifacts/schema/latency-bucket.schema.json +63 -0
- package/artifacts/schema/live-session-response.schema.json +41 -0
- package/artifacts/schema/mail-outcome.schema.json +20 -0
- package/artifacts/schema/mail-template-preview-request.schema.json +35 -0
- package/artifacts/schema/mail-template-preview-response.schema.json +31 -0
- package/artifacts/schema/mail-template-problem-details.schema.json +24 -0
- package/artifacts/schema/mcp-consent-grant-list-response.schema.json +52 -0
- package/artifacts/schema/mcp-consent-grant.schema.json +39 -0
- package/artifacts/schema/mcp-robot-datasheet.schema.json +115 -0
- package/artifacts/schema/mcp-role-preview-response.schema.json +134 -0
- package/artifacts/schema/missing-asset-query.schema.json +11 -0
- package/artifacts/schema/oauth-authorize-query.schema.json +47 -0
- package/artifacts/schema/oauth-redirect-response.schema.json +15 -0
- package/artifacts/schema/oauth-token-request.schema.json +47 -0
- package/artifacts/schema/oauth-token-response.schema.json +38 -0
- package/artifacts/schema/org-alerts-query.schema.json +15 -0
- package/artifacts/schema/org-event-dropped.schema.json +26 -0
- package/artifacts/schema/org-event-replay.schema.json +97 -0
- package/artifacts/schema/org-event-subscribe.schema.json +14 -0
- package/artifacts/schema/org-event-unsubscribe.schema.json +14 -0
- package/artifacts/schema/org-event.schema.json +75 -0
- package/artifacts/schema/org-firing-alerts-response.schema.json +178 -0
- package/artifacts/schema/org-health-query.schema.json +13 -0
- package/artifacts/schema/org-latency-query.schema.json +42 -0
- package/artifacts/schema/org-latency-response.schema.json +124 -0
- package/artifacts/schema/org-quota-usage-counts.schema.json +42 -0
- package/artifacts/schema/org-quota-usage.schema.json +102 -0
- package/artifacts/schema/org-quotas.schema.json +51 -0
- package/artifacts/schema/org-usage-query.schema.json +19 -0
- package/artifacts/schema/org-usage-response.schema.json +77 -0
- package/artifacts/schema/org.schema.json +30 -0
- package/artifacts/schema/parameter-invalid-details.schema.json +37 -0
- package/artifacts/schema/parameter-spec.schema.json +120 -0
- package/artifacts/schema/parameter-violation.schema.json +24 -0
- package/artifacts/schema/password-change-request.schema.json +21 -0
- package/artifacts/schema/password-reset-confirm.schema.json +19 -0
- package/artifacts/schema/password-reset-request.schema.json +14 -0
- package/artifacts/schema/patch-app-oidc-provider-request.schema.json +50 -0
- package/artifacts/schema/patch-app-user-request.schema.json +34 -0
- package/artifacts/schema/patch-auth-me-request.schema.json +22 -0
- package/artifacts/schema/patch-fleetless-user-request.schema.json +20 -0
- package/artifacts/schema/patch-org-request.schema.json +15 -0
- package/artifacts/schema/patch-org-response.schema.json +40 -0
- package/artifacts/schema/patch-robot-request.schema.json +15 -0
- package/artifacts/schema/patch-robot-response.schema.json +40 -0
- package/artifacts/schema/pending-team-invite-list-response.schema.json +52 -0
- package/artifacts/schema/pending-team-invite.schema.json +39 -0
- package/artifacts/schema/protected-resource-metadata.schema.json +41 -0
- package/artifacts/schema/publish-config-response.schema.json +21 -0
- package/artifacts/schema/publish-request.schema.json +17 -0
- package/artifacts/schema/publisher-config.schema.json +285 -0
- package/artifacts/schema/put-app-auth-config-request.schema.json +93 -0
- package/artifacts/schema/put-app-mail-template-request.schema.json +35 -0
- package/artifacts/schema/put-config-draft-request.schema.json +13 -0
- package/artifacts/schema/put-datapoint-display-request.schema.json +31 -0
- package/artifacts/schema/put-robot-details-request.schema.json +41 -0
- package/artifacts/schema/put-robot-details-response.schema.json +43 -0
- package/artifacts/schema/rate-limit-details.schema.json +15 -0
- package/artifacts/schema/refresh-request.schema.json +13 -0
- package/artifacts/schema/release-live-query.schema.json +13 -0
- package/artifacts/schema/rename-slug-request.schema.json +23 -0
- package/artifacts/schema/rename-slug-response.schema.json +24 -0
- package/artifacts/schema/resource-health-event.schema.json +72 -0
- package/artifacts/schema/resource-health-list-response.schema.json +80 -0
- package/artifacts/schema/resource-health-state.schema.json +68 -0
- package/artifacts/schema/robot-config-doc.schema.json +1616 -0
- package/artifacts/schema/robot-delete-query.schema.json +12 -0
- package/artifacts/schema/robot-deletion-summary.schema.json +63 -0
- package/artifacts/schema/robot-detail-response.schema.json +262 -0
- package/artifacts/schema/robot-details-doc.schema.json +33 -0
- package/artifacts/schema/robot-jobs-response.schema.json +119 -0
- package/artifacts/schema/robot-latency-series.schema.json +81 -0
- package/artifacts/schema/robot-list-item.schema.json +94 -0
- package/artifacts/schema/robot-list-response.schema.json +106 -0
- package/artifacts/schema/robot.schema.json +30 -0
- package/artifacts/schema/role-list-response.schema.json +48 -0
- package/artifacts/schema/role-permissions.schema.json +61 -0
- package/artifacts/schema/role.schema.json +35 -0
- package/artifacts/schema/ros-graph.schema.json +99 -0
- package/artifacts/schema/server-key-list-response.schema.json +64 -0
- package/artifacts/schema/server-key.schema.json +51 -0
- package/artifacts/schema/service-call-response.schema.json +13 -0
- package/artifacts/schema/service-config.schema.json +198 -0
- package/artifacts/schema/session-tokens.schema.json +28 -0
- package/artifacts/schema/sign-up-request.schema.json +26 -0
- package/artifacts/schema/sign-up-response.schema.json +127 -0
- package/artifacts/schema/slug-usage-response.schema.json +32 -0
- package/artifacts/schema/snapshot-header.schema.json +44 -0
- package/artifacts/schema/snapshot-meta-response.schema.json +85 -0
- package/artifacts/schema/subscribe-error.schema.json +31 -0
- package/artifacts/schema/team-invite.schema.json +57 -0
- package/artifacts/schema/tier-change-request.schema.json +17 -0
- package/artifacts/schema/type-definition.schema.json +144 -0
- package/artifacts/schema/types-response.schema.json +156 -0
- package/artifacts/schema/update-app-request.schema.json +32 -0
- package/artifacts/schema/urdf-completeness.schema.json +50 -0
- package/artifacts/schema/validation-issue.schema.json +43 -0
- package/artifacts/schema/waitlist-request.schema.json +15 -0
- package/artifacts/schema-outgoing/bridge-asset-progress.schema.json +102 -0
- package/artifacts/schema-outgoing/bridge-assets-available.schema.json +26 -0
- package/artifacts/schema-outgoing/bridge-camera-state.schema.json +80 -0
- package/artifacts/schema-outgoing/bridge-config-applied.schema.json +69 -0
- package/artifacts/schema-outgoing/bridge-hello.schema.json +68 -0
- package/artifacts/schema-outgoing/bridge-introspect.schema.json +119 -0
- package/artifacts/schema-outgoing/bridge-job-lost.schema.json +23 -0
- package/artifacts/schema-outgoing/bridge-job-update.schema.json +102 -0
- package/artifacts/schema-outgoing/bridge-pong.schema.json +20 -0
- package/artifacts/schema-outgoing/bridge-type-definitions.schema.json +174 -0
- package/artifacts/schema-outgoing/datapoint-frame.schema.json +29 -0
- package/artifacts/schema-outgoing/snapshot-header.schema.json +45 -0
- package/dist/alerts.d.ts +255 -0
- package/dist/alerts.js +193 -0
- package/dist/app-users.d.ts +606 -0
- package/dist/app-users.js +696 -0
- package/dist/apps.d.ts +175 -0
- package/dist/apps.js +267 -0
- package/dist/assets.d.ts +434 -0
- package/dist/assets.js +546 -0
- package/dist/audit.d.ts +129 -0
- package/dist/audit.js +238 -0
- package/dist/client-auth.d.ts +409 -0
- package/dist/client-auth.js +487 -0
- package/dist/common.d.ts +186 -0
- package/dist/common.js +199 -0
- package/dist/config-issues.d.ts +175 -0
- package/dist/config-issues.js +339 -0
- package/dist/config.d.ts +862 -0
- package/dist/config.js +1988 -0
- package/dist/errors.d.ts +52 -0
- package/dist/errors.js +786 -0
- package/dist/identity.d.ts +549 -0
- package/dist/identity.js +503 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +51 -0
- package/dist/introspection.d.ts +99 -0
- package/dist/introspection.js +97 -0
- package/dist/jobs.d.ts +334 -0
- package/dist/jobs.js +345 -0
- package/dist/mcp.d.ts +239 -0
- package/dist/mcp.js +153 -0
- package/dist/oauth.d.ts +344 -0
- package/dist/oauth.js +488 -0
- package/dist/protocol.d.ts +781 -0
- package/dist/protocol.js +715 -0
- package/dist/realtime.d.ts +494 -0
- package/dist/realtime.js +512 -0
- package/dist/rest.d.ts +1989 -0
- package/dist/rest.js +1963 -0
- package/dist/routes.d.ts +94 -0
- package/dist/routes.js +2298 -0
- package/package.json +61 -0
package/dist/jobs.js
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { slug, wireSeqCursor, wireTimestampMs } from './common.js';
|
|
4
|
+
/**
|
|
5
|
+
* Jobs (spec §6.1, §11.3): one running unit of work on a robot — an action
|
|
6
|
+
* goal or a service call — with an id both sides know, so bridge and cloud
|
|
7
|
+
* stay in sync across a disconnect.
|
|
8
|
+
*
|
|
9
|
+
* Two rules shape everything here:
|
|
10
|
+
*
|
|
11
|
+
* 1. **State is observed by slug, not by id.** The id is informative (§11.3);
|
|
12
|
+
* a client watches `robot × slug` and sees whatever job is running there,
|
|
13
|
+
* which is also why every observer of a slug sees the same job.
|
|
14
|
+
* 2. **`lost` is a real outcome and must be said out loud** (§6.1). Job state
|
|
15
|
+
* lives only in the bridge's memory; if it restarts mid-job, the results
|
|
16
|
+
* are gone. The cloud then marks the job `lost` — never leaves it reading
|
|
17
|
+
* "running" because nobody contradicted it. A system that reports a
|
|
18
|
+
* machine is still working when it does not know is worse than one that
|
|
19
|
+
* admits it lost track.
|
|
20
|
+
*/
|
|
21
|
+
export const jobState = z.enum(['running', 'succeeded', 'failed', 'cancelled', 'lost']);
|
|
22
|
+
export const job = z.object({
|
|
23
|
+
id: z.uuid().meta({
|
|
24
|
+
description: 'The job\'s id, minted by the cloud when the invocation is accepted. Informative: state is observed by slug, and this id is what a cancel names when a caller wants to stop one specific job rather than whatever is running.',
|
|
25
|
+
}),
|
|
26
|
+
robot_id: z.uuid().meta({ description: 'The robot this job is running on.' }),
|
|
27
|
+
slug: slug.meta({
|
|
28
|
+
description: 'The action or service this job is running, as the published configuration exposes it. One slug carries one job at a time, so every observer of that slug sees the same one.',
|
|
29
|
+
}),
|
|
30
|
+
state: jobState.meta({
|
|
31
|
+
description: 'Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `lost` is a real outcome — the bridge restarted mid-job and the result is gone — and is said out loud rather than left reading `running` because nobody contradicted it.',
|
|
32
|
+
}),
|
|
33
|
+
started_at: z.iso.datetime().meta({
|
|
34
|
+
description: 'When the cloud minted this job, as an ISO 8601 timestamp. For a job adopted from a reconnecting bridge it is **adoption time**, not the real start, because the cloud never minted it and has no honest alternative.',
|
|
35
|
+
}),
|
|
36
|
+
updated_at: z.iso.datetime().meta({
|
|
37
|
+
description: 'When this job last changed, as an ISO 8601 timestamp.',
|
|
38
|
+
}),
|
|
39
|
+
/**
|
|
40
|
+
* A monotonic counter, ascending in mint order (W7), and the **named**
|
|
41
|
+
* tiebreaker for any listing that claims an order.
|
|
42
|
+
*
|
|
43
|
+
* `started_at` is not a total order: two jobs minted in the same millisecond
|
|
44
|
+
* sort against each other arbitrarily, and arbitrarily means *differently on
|
|
45
|
+
* each query* — so `GET /api/robots/:id/jobs`, which documents "newest
|
|
46
|
+
* first", can show one twice and the other not at all. Exactly the defect
|
|
47
|
+
* `auditEvent.seq` was added for in W6b, in a route the same wave shipped.
|
|
48
|
+
*
|
|
49
|
+
* **Scoped honestly: per cloud process, per run.** Job state lives in memory
|
|
50
|
+
* (§6.1 — that is why `lost` exists at all), so this counter restarts when
|
|
51
|
+
* the cloud does, alongside the jobs it orders. Sound, because it only ever
|
|
52
|
+
* orders jobs that coexist in one registry — and stated, because a reader
|
|
53
|
+
* who assumed `auditEvent.seq`'s durable semantics would be wrong.
|
|
54
|
+
*/
|
|
55
|
+
seq: z.number().int().positive().meta({
|
|
56
|
+
description: 'A monotonic counter ascending in mint order, and the named tiebreaker for any listing that claims one — `started_at` alone is not a total order. Scoped per cloud process and per run: job state lives in memory, so this restarts with the registry it orders.',
|
|
57
|
+
}),
|
|
58
|
+
result: z.unknown().nullable().meta({
|
|
59
|
+
description: 'What the call returned once it succeeded, shaped by the ROS action or service itself. `null` until then, and for a job that did not succeed.',
|
|
60
|
+
}),
|
|
61
|
+
/**
|
|
62
|
+
* Present on `failed`; a human message, plus a code where one exists.
|
|
63
|
+
*
|
|
64
|
+
* `details` exists because a refusal that carries only prose forces every
|
|
65
|
+
* consumer to parse it. W6b shipped `job_queue_full` with a documented
|
|
66
|
+
* `{limit, queued}` payload and **nowhere to put it**: the bridge reports a
|
|
67
|
+
* full queue as a job error, this shape had no `details`, and so the numbers
|
|
68
|
+
* were formatted into the message and lost. The console then rendered a
|
|
69
|
+
* "wait for one of N to finish" alert from a shape nothing in the system
|
|
70
|
+
* produced, and its test built that shape by hand — three repos agreeing
|
|
71
|
+
* with each other about a payload none of them exchanged (Momus, W6b
|
|
72
|
+
* review).
|
|
73
|
+
*
|
|
74
|
+
* Optional, because most job errors have nothing structured to add. Where a
|
|
75
|
+
* code has a documented payload — `job_queue_full` has
|
|
76
|
+
* `jobQueueFullDetails` — it belongs here, not in the sentence.
|
|
77
|
+
*/
|
|
78
|
+
error: z
|
|
79
|
+
.object({
|
|
80
|
+
code: z.string().min(1).meta({
|
|
81
|
+
description: 'A machine-readable code for the failure, such as `job_queue_full`, where one exists for it.',
|
|
82
|
+
}),
|
|
83
|
+
message: z.string().min(1).meta({
|
|
84
|
+
description: 'A human-readable sentence saying what went wrong.',
|
|
85
|
+
}),
|
|
86
|
+
details: z.unknown().optional().meta({
|
|
87
|
+
description: 'The structured payload belonging to `code`, for the codes that document one — `job_queue_full` carries its `limit` and its `queued` count here. Absent for a failure with nothing structured to add, which is most of them.',
|
|
88
|
+
}),
|
|
89
|
+
})
|
|
90
|
+
.nullable()
|
|
91
|
+
.meta({
|
|
92
|
+
description: 'Why the job failed: a human `message`, a `code` where one exists, and `details` for the codes that carry a documented payload. `null` unless `state` is `failed`.',
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
/**
|
|
96
|
+
* One update about a job, pushed to subscribers of its slug.
|
|
97
|
+
*
|
|
98
|
+
* `timestamp_ms` is the bridge's capture time, exactly as for a datapoint
|
|
99
|
+
* (§6.3 says action feedback carries it too) — so a client computes the age
|
|
100
|
+
* of a progress report the same way it computes the age of a sensor value,
|
|
101
|
+
* and a burst of late-delivered feedback after a reconnect is visibly late
|
|
102
|
+
* rather than looking current.
|
|
103
|
+
*/
|
|
104
|
+
export const jobEvent = z.object({
|
|
105
|
+
type: z.literal('job'),
|
|
106
|
+
robot_id: z.uuid(),
|
|
107
|
+
slug,
|
|
108
|
+
job,
|
|
109
|
+
/** Action feedback, if this update carries any. */
|
|
110
|
+
feedback: z.unknown().nullable(),
|
|
111
|
+
/** 0..1 when the action reports progress; null when it does not. */
|
|
112
|
+
progress: z.number().min(0).max(1).nullable(),
|
|
113
|
+
timestamp_ms: z.number().int().nonnegative(),
|
|
114
|
+
});
|
|
115
|
+
/**
|
|
116
|
+
* What a busy refusal tells the caller (spec §11.3: "inkl. Information, was
|
|
117
|
+
* läuft"). A refusal that only says "busy" forces the caller to guess whether
|
|
118
|
+
* to wait or to give up.
|
|
119
|
+
*/
|
|
120
|
+
export const busyDetails = z.object({
|
|
121
|
+
running: job,
|
|
122
|
+
});
|
|
123
|
+
/**
|
|
124
|
+
* What a `publisher_busy` refusal tells the caller (spec §6.4).
|
|
125
|
+
*
|
|
126
|
+
* "Another caller is publishing and has not been quiet long enough" names a
|
|
127
|
+
* state and no action: the caller does not know how much longer, because
|
|
128
|
+
* `quiet_timeout_ms` lives in the configuration document, which a client app
|
|
129
|
+
* never reads. Without a number they busy-loop — on the one verb that moves
|
|
130
|
+
* a machine, on a platform with no rate limiting. So the refusal carries the
|
|
131
|
+
* wait itself.
|
|
132
|
+
*
|
|
133
|
+
* `holder` is deliberately absent: it would name another end user to a
|
|
134
|
+
* caller who may have no right to know they exist.
|
|
135
|
+
*/
|
|
136
|
+
export const publisherBusyDetails = z.object({
|
|
137
|
+
/** The configured silence a holder must leave before anyone else may publish. */
|
|
138
|
+
quiet_timeout_ms: z.number().int().nonnegative(),
|
|
139
|
+
/** How much of that silence is still outstanding, now. */
|
|
140
|
+
retry_after_ms: z.number().int().nonnegative(),
|
|
141
|
+
});
|
|
142
|
+
/**
|
|
143
|
+
* What a `job_queue_full` refusal tells the caller (W6b).
|
|
144
|
+
*
|
|
145
|
+
* Both numbers, not just the limit: `limit` alone says how big the queue is
|
|
146
|
+
* and nothing about whether waiting will help, and `queued` alone cannot be
|
|
147
|
+
* read without knowing the bound. Together they are the only two facts a
|
|
148
|
+
* caller needs to decide between retrying and giving up.
|
|
149
|
+
*/
|
|
150
|
+
export const jobQueueFullDetails = z.object({
|
|
151
|
+
/** The bridge's bound on queued jobs. */
|
|
152
|
+
limit: z.number().int().positive(),
|
|
153
|
+
/** How many are queued right now — `>= limit` when this refusal is sent. */
|
|
154
|
+
queued: z.number().int().nonnegative(),
|
|
155
|
+
});
|
|
156
|
+
/** A page of run history is bounded; 200 is what one console screen can ever want. */
|
|
157
|
+
export const JOB_RUN_PAGE_MAX = 200;
|
|
158
|
+
/**
|
|
159
|
+
* Job runs keep the audit log's retention, and that is not a coincidence:
|
|
160
|
+
* every invoke already writes an `action.invoked` audit event. A different
|
|
161
|
+
* figure here creates a window in which the audit log shows a call whose
|
|
162
|
+
* outcome has already been deleted — a state no developer can be expected to
|
|
163
|
+
* read as anything but a bug.
|
|
164
|
+
*/
|
|
165
|
+
export const JOB_RUN_RETENTION_DAYS = 90;
|
|
166
|
+
/**
|
|
167
|
+
* Who invoked a run.
|
|
168
|
+
*
|
|
169
|
+
* Deliberately **not** `auditActor`: that enum carries `bridge` as a fourth
|
|
170
|
+
* case, and a bridge invokes nothing. An enum that names an impossible case
|
|
171
|
+
* invites every reader to handle it.
|
|
172
|
+
*
|
|
173
|
+
* **`app_user` is what a client-app caller writes now, and `end_user` stays**
|
|
174
|
+
* (app-user auth, D1). The seam this comment used to describe — two names for
|
|
175
|
+
* two ways into one merged pool — is settled: the two identity spaces are
|
|
176
|
+
* separate tables again, and `app_user` is a row in `app_users`, belonging to
|
|
177
|
+
* exactly one app. `end_user` is kept for the same reason `auditActor.kind`
|
|
178
|
+
* keeps it: a job run is history, and every row written before the cut carries
|
|
179
|
+
* it. Removing the member would make the whole trail unparseable to a client
|
|
180
|
+
* that validates, which is the one thing a history shape must never do.
|
|
181
|
+
*/
|
|
182
|
+
export const jobActor = z.object({
|
|
183
|
+
kind: z.enum(['developer', 'end_user', 'app_user', 'server_key']).meta({
|
|
184
|
+
description: 'What the caller was acting as: a `developer` in the console, an `app_user` of one app, or a `server_key` used by server-side code. A bridge invokes nothing, so it is deliberately not a case here. `end_user` appears only on runs recorded before app users replaced the organisation-wide user pool — it is kept so a history page can still render them, and nothing writes it any more.',
|
|
185
|
+
}),
|
|
186
|
+
id: z.uuid().meta({
|
|
187
|
+
description: 'The id of the Fleetless user, app user or server key that invoked the run.',
|
|
188
|
+
}),
|
|
189
|
+
/**
|
|
190
|
+
* The email for a person, the key's `name` for a server key. A display
|
|
191
|
+
* snapshot taken at invoke time: renaming a key afterwards does not rewrite
|
|
192
|
+
* history, which is the point of storing it rather than joining.
|
|
193
|
+
*/
|
|
194
|
+
label: z.string().min(1).max(200).meta({
|
|
195
|
+
description: 'A display name taken at invoke time — the email for a Fleetless user or an app user, the key\'s own name for a server key. Storing it rather than joining is the point: renaming a key afterwards does not rewrite history.',
|
|
196
|
+
}),
|
|
197
|
+
});
|
|
198
|
+
export const jobRunKind = z.enum(['action', 'service']);
|
|
199
|
+
/**
|
|
200
|
+
* One durable record of one invocation (spec `2026-08-20-timeseries-and-run-history`,
|
|
201
|
+
* D2). One row per run, never one per event: the per-event timeline's write rate
|
|
202
|
+
* is set by the bridge, and a throttled log that cannot say it was throttled is
|
|
203
|
+
* the instrument this codebase refuses everywhere else. The live timeline is
|
|
204
|
+
* delivered in full by realtime, for as long as somebody is watching.
|
|
205
|
+
*/
|
|
206
|
+
export const jobRun = z.object({
|
|
207
|
+
id: z.uuid().meta({
|
|
208
|
+
description: 'The run\'s id, which is the same id the invocation was answered with — so a caller that kept a job id can find its durable record here later.',
|
|
209
|
+
}),
|
|
210
|
+
robot_id: z.uuid().meta({ description: 'The robot the run happened on.' }),
|
|
211
|
+
slug: slug.meta({
|
|
212
|
+
description: 'The action or service that was invoked, as the published configuration exposed it at the time.',
|
|
213
|
+
}),
|
|
214
|
+
kind: jobRunKind.meta({
|
|
215
|
+
description: 'Whether the slug was an `action` or a `service`.',
|
|
216
|
+
}),
|
|
217
|
+
state: jobState.meta({
|
|
218
|
+
description: 'How the run ended, or `running` while it is still going. `lost` means the bridge restarted mid-run and the outcome is unknowable rather than unknown.',
|
|
219
|
+
}),
|
|
220
|
+
started_at: z.iso.datetime().meta({
|
|
221
|
+
description: 'When the run started, as an ISO 8601 timestamp. Runs are listed and filtered by this instant.',
|
|
222
|
+
}),
|
|
223
|
+
ended_at: z.iso.datetime().nullable().meta({
|
|
224
|
+
description: 'When the run finished, as an ISO 8601 timestamp. `null` while it is still `running` — a run has an end only once it has one.',
|
|
225
|
+
}),
|
|
226
|
+
duration_ms: z.number().int().nonnegative().nullable().meta({
|
|
227
|
+
description: 'How long the run took, in milliseconds. `null` while it is still `running`, never `0` standing in for "nothing so far".',
|
|
228
|
+
}),
|
|
229
|
+
result: z.unknown().nullable().meta({
|
|
230
|
+
description: 'What the action or service returned once it succeeded, shaped by ROS itself. `null` otherwise.',
|
|
231
|
+
}),
|
|
232
|
+
error: z
|
|
233
|
+
.object({
|
|
234
|
+
code: z.string().min(1).meta({
|
|
235
|
+
description: 'A machine-readable code for the failure, such as `job_queue_full`, where one exists for it.',
|
|
236
|
+
}),
|
|
237
|
+
message: z.string().min(1).meta({
|
|
238
|
+
description: 'A human-readable sentence saying what went wrong.',
|
|
239
|
+
}),
|
|
240
|
+
details: z.unknown().optional().meta({
|
|
241
|
+
description: 'The structured payload belonging to `code`, for the codes that document one. Absent for a failure with nothing structured to add.',
|
|
242
|
+
}),
|
|
243
|
+
})
|
|
244
|
+
.nullable()
|
|
245
|
+
.meta({
|
|
246
|
+
description: 'Why the run failed — a `message`, a `code` where one exists, and the structured `details` some codes carry. `null` unless it failed.',
|
|
247
|
+
}),
|
|
248
|
+
actor: jobActor.meta({
|
|
249
|
+
description: 'Who invoked the run, and what they were acting as at the time.',
|
|
250
|
+
}),
|
|
251
|
+
/**
|
|
252
|
+
* **Durable, unlike `job.seq`.** That one is a per-process counter that
|
|
253
|
+
* restarts with the cloud; this is a postgres `bigserial` and is the cursor
|
|
254
|
+
* `before_seq` walks.
|
|
255
|
+
*/
|
|
256
|
+
seq: z.number().int().positive().meta({
|
|
257
|
+
description: 'The durable cursor this history is ordered and paged by. Unlike `job.seq` it does not restart when the cloud does; it is the value a caller sends back as `before_seq`.',
|
|
258
|
+
}),
|
|
259
|
+
progress: z.number().min(0).max(1).nullable().meta({
|
|
260
|
+
description: 'How far a still-running run has got, as a fraction from `0` to `1`, read live from the in-memory registry. `null` means **not known right now** — after a cloud restart, before the bridge reconnects — and never a `0` standing in for \"no progress yet\".',
|
|
261
|
+
}),
|
|
262
|
+
feedback: z.unknown().nullable().meta({
|
|
263
|
+
description: 'The most recent action feedback for a run that is still running, shaped by the ROS action. Live-only, so it is `null` for every settled run and whenever the registry has nothing.',
|
|
264
|
+
}),
|
|
265
|
+
});
|
|
266
|
+
export const jobRunQuery = z
|
|
267
|
+
.object({
|
|
268
|
+
before_seq: wireSeqCursor.optional().meta({
|
|
269
|
+
description: 'Return only runs with a `seq` below this value — the next, older page. Send back the `next_cursor` of the previous response rather than computing one.',
|
|
270
|
+
}),
|
|
271
|
+
limit: z
|
|
272
|
+
.union([z.string().regex(/^\d{1,4}$/), z.number().int()])
|
|
273
|
+
.transform((v) => Number(v))
|
|
274
|
+
.pipe(z.number().int().positive().max(JOB_RUN_PAGE_MAX))
|
|
275
|
+
.optional()
|
|
276
|
+
.meta({
|
|
277
|
+
description: 'How many runs to return, from `1` to `200`. Absent means `100`. It arrives on the query string, so a numeric string and a number are both accepted.',
|
|
278
|
+
}),
|
|
279
|
+
robot_id: z.uuid().optional().meta({
|
|
280
|
+
description: 'Only runs on this robot. Absent means every robot in the organisation.',
|
|
281
|
+
}),
|
|
282
|
+
slug: slug.optional().meta({
|
|
283
|
+
description: 'Only runs of this action or service.',
|
|
284
|
+
}),
|
|
285
|
+
state: jobState.optional().meta({
|
|
286
|
+
description: 'Only runs in this state — `running`, `succeeded`, `failed`, `cancelled` or `lost`.',
|
|
287
|
+
}),
|
|
288
|
+
kind: jobRunKind.optional().meta({
|
|
289
|
+
description: 'Only `action` runs, or only `service` runs.',
|
|
290
|
+
}),
|
|
291
|
+
from_ms: wireTimestampMs.optional().meta({
|
|
292
|
+
description: 'Only runs that started at or after this unix timestamp in milliseconds. Together with `to_ms` the window is half-open, `[from, to)`, so adjacent windows tile without counting a run twice.',
|
|
293
|
+
}),
|
|
294
|
+
to_ms: wireTimestampMs.optional().meta({
|
|
295
|
+
description: 'Only runs that started **before** this unix timestamp in milliseconds. The window is half-open, so a run starting exactly on `to_ms` belongs to the next one.',
|
|
296
|
+
}),
|
|
297
|
+
})
|
|
298
|
+
.strict();
|
|
299
|
+
export const jobRunListResponse = z.object({
|
|
300
|
+
runs: z.array(jobRun).meta({
|
|
301
|
+
description: 'This page of runs, newest first by `seq`. Empty means the filter matched nothing, not that the history is gone.',
|
|
302
|
+
}),
|
|
303
|
+
/**
|
|
304
|
+
* The `seq` a caller sends as `before_seq` to keep reading — or `null` when
|
|
305
|
+
* there is nothing further. **`null` means the end, and that is a promise
|
|
306
|
+
* rather than an observation.** A caller who instead compares `runs.length`
|
|
307
|
+
* against `limit` is wrong the moment a filter makes a page thin.
|
|
308
|
+
*/
|
|
309
|
+
next_cursor: z.number().int().positive().nullable().meta({
|
|
310
|
+
description: 'The `seq` to send as `before_seq` to keep reading, or `null` when there is nothing further. **`null` is a promise, not an observation** — a caller who instead compares the page length against `limit` is wrong the moment a filter makes a page thin.',
|
|
311
|
+
}),
|
|
312
|
+
});
|
|
313
|
+
/**
|
|
314
|
+
* The overview tile's three numbers, over a window **the caller names**.
|
|
315
|
+
*
|
|
316
|
+
* `since_ms` rather than "today": which day that is, only the browser knows. A
|
|
317
|
+
* cloud that picks its own day boundary shows a developer in another timezone a
|
|
318
|
+
* number they cannot reproduce. Echoed back so a rendered tile can say which
|
|
319
|
+
* window it is describing.
|
|
320
|
+
*/
|
|
321
|
+
/**
|
|
322
|
+
* `GET /api/org/jobs/summary`'s query: the window, and nothing else.
|
|
323
|
+
*
|
|
324
|
+
* **`since_ms` is required and has no default.** Which day "today" is, only
|
|
325
|
+
* the browser knows; a cloud that picked its own boundary would show a
|
|
326
|
+
* developer in another timezone a number they cannot reproduce from anything
|
|
327
|
+
* in front of them. The absence of a default is the contract here, not an
|
|
328
|
+
* omission — see `jobRunSummary`, which echoes the window back so a rendered
|
|
329
|
+
* tile can say what it is describing.
|
|
330
|
+
*
|
|
331
|
+
* Its own shape rather than a slice of `jobRunQuery`: pagination and filters
|
|
332
|
+
* mean nothing to an aggregate, and `.strict()` would refuse them anyway, so
|
|
333
|
+
* borrowing that schema would advertise seven parameters the route ignores.
|
|
334
|
+
*
|
|
335
|
+
* `.strict()` for `jobRunQuery`'s reason — a mistyped `since_mss` that is
|
|
336
|
+
* silently ignored answers `200` over a window nobody chose, which is worse
|
|
337
|
+
* than a refusal because it looks like data.
|
|
338
|
+
*/
|
|
339
|
+
export const jobRunSummaryQuery = z.object({ since_ms: wireTimestampMs }).strict();
|
|
340
|
+
export const jobRunSummary = z.object({
|
|
341
|
+
running: z.number().int().nonnegative(),
|
|
342
|
+
started: z.number().int().nonnegative(),
|
|
343
|
+
failed: z.number().int().nonnegative(),
|
|
344
|
+
since_ms: z.number().int().nonnegative(),
|
|
345
|
+
});
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* The MCP server of §17: **one** remote MCP endpoint for the whole platform,
|
|
4
|
+
* whose tools are the exposed services and datapoints the signed-in user's
|
|
5
|
+
* roles permit. The per-app `/mcp/<identifier>` servers this file once
|
|
6
|
+
* described were deleted by the org-central identity redesign (D5/D6).
|
|
7
|
+
*
|
|
8
|
+
* **This file describes the seam, not the protocol.** The MCP messages
|
|
9
|
+
* themselves (`initialize`, `tools/list`, `tools/call`) are defined by the
|
|
10
|
+
* Model Context Protocol and implemented with its official SDK — writing our
|
|
11
|
+
* own zod copies of them would create a second source of truth for somebody
|
|
12
|
+
* else's specification, which is the one thing this package exists to avoid.
|
|
13
|
+
* What lives here is what *Fleetless* decides: which revision we speak, where
|
|
14
|
+
* the endpoint is, how a tool is named, and what the console is shown before
|
|
15
|
+
* an end user ever connects.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The protocol revision W7c speaks. Chosen with André on 2026-08-18 over the
|
|
19
|
+
* newer `2026-07-28`.
|
|
20
|
+
*
|
|
21
|
+
* This is the latest revision the **stable** MCP TypeScript SDK ships, and it
|
|
22
|
+
* negotiates down to `2024-11-05`, so it covers the AI tools that exist today.
|
|
23
|
+
* `2026-07-28` is real and is where the protocol is going — it *removes*
|
|
24
|
+
* Streamable HTTP's session ids, the standalone SSE channel and resumability,
|
|
25
|
+
* and servers speaking only it answer `405` to GET and DELETE.
|
|
26
|
+
*
|
|
27
|
+
* **Which is why this server is stateless anyway.** Building sessions we would
|
|
28
|
+
* have to delete again is work in the wrong direction, and a per-process
|
|
29
|
+
* session map is the assumption that breaks at the second cloud instance —
|
|
30
|
+
* the register already carries one row of exactly that shape
|
|
31
|
+
* (`max_realtime_connections`), and W8 is where a second instance appears.
|
|
32
|
+
*/
|
|
33
|
+
export declare const MCP_PROTOCOL_VERSION: "2025-11-25";
|
|
34
|
+
/**
|
|
35
|
+
* The path of the **central** MCP server — what a *Fleetless user* pastes into
|
|
36
|
+
* their AI tool, appended to the cloud's public base URL.
|
|
37
|
+
*
|
|
38
|
+
* **Not parameterised, and that is now a statement rather than the absence of
|
|
39
|
+
* one.** The central endpoint serves the org's team with the console tool
|
|
40
|
+
* family (2026-09-05, D7); an app's users reach a different endpoint, whose
|
|
41
|
+
* path `mcpAppEndpointPath` builds. Two constants for two audiences, so a call
|
|
42
|
+
* site says which it means instead of an argument deciding it.
|
|
43
|
+
*
|
|
44
|
+
* **The canonical URL is `<PUBLIC_API_BASE_URL>${MCP_ENDPOINT_PATH}`, not the
|
|
45
|
+
* friendly alias.** `mcp.fleetless.dev` is a reverse proxy onto the same
|
|
46
|
+
* cloud, but the cloud mints every OAuth issuer, resource and `aud` from
|
|
47
|
+
* `PUBLIC_API_BASE_URL` and compares the token's `aud` against that string —
|
|
48
|
+
* never against the request's `Host`. Hand out the canonical one.
|
|
49
|
+
*/
|
|
50
|
+
export declare const MCP_ENDPOINT_PATH: "/mcp";
|
|
51
|
+
/**
|
|
52
|
+
* The path of **one app's** MCP server (D7) — what an app user pastes into
|
|
53
|
+
* their AI tool, served only while the app's `appAuthConfig.mcp_enabled` is on.
|
|
54
|
+
*
|
|
55
|
+
* A helper rather than a template literal at four call sites, for
|
|
56
|
+
* `OAUTH_PATHS`' reason: the console shows this string with a copy button, the
|
|
57
|
+
* cloud registers the route from it, and the docs render it. A path spelled in
|
|
58
|
+
* three places is a path two of them will one day spell differently — and this
|
|
59
|
+
* repository has already paid for exactly that, with an `idpStart` entry naming
|
|
60
|
+
* a route the cloud had deleted.
|
|
61
|
+
*
|
|
62
|
+
* **This is the path, not the URL.** Append it to `PUBLIC_API_BASE_URL`, the
|
|
63
|
+
* canonical origin the cloud mints every issuer and audience from, rather than
|
|
64
|
+
* to the friendly `mcp.fleetless.dev` alias — a token's `aud` is compared
|
|
65
|
+
* against the canonical string and never against the request's `Host`.
|
|
66
|
+
*
|
|
67
|
+
* There was a `mcpEndpointPath(appIdentifier)` before, deleted with the per-app
|
|
68
|
+
* endpoint in the central-MCP cut and remembered here because the shape of that
|
|
69
|
+
* mistake is worth not repeating: the console kept offering a copy button for a
|
|
70
|
+
* URL that answered `404`. This one exists **with** its endpoint, and the
|
|
71
|
+
* cloud's route-manifest test is what keeps them together.
|
|
72
|
+
*/
|
|
73
|
+
export declare function mcpAppEndpointPath(appIdentifier: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* **Every path one app's MCP server answers on, built from its identifier
|
|
76
|
+
* once** (D7).
|
|
77
|
+
*
|
|
78
|
+
* Six strings, and each of them is spelled in at least three places that
|
|
79
|
+
* cannot see one another: the cloud registers the route, the console renders a
|
|
80
|
+
* copy button beside it, the reverse proxy in front of `mcp.fleetless.dev`
|
|
81
|
+
* routes on it, and the documentation site prints it. `mcpAppEndpointPath`
|
|
82
|
+
* already made that argument for the endpoint alone; the five paths around it
|
|
83
|
+
* are worse to get wrong, because an MCP client **discovers** them — it reads
|
|
84
|
+
* the two metadata documents and follows what they say — so a divergence is
|
|
85
|
+
* not a `404` a developer sees, it is a sign-in that stops halfway in somebody
|
|
86
|
+
* else's client.
|
|
87
|
+
*
|
|
88
|
+
* **The two `.well-known` paths are not ours to choose.** RFC 9728 §3.1 and
|
|
89
|
+
* RFC 8414 §3 both say the same thing: take the resource (or issuer) URL, and
|
|
90
|
+
* insert `/.well-known/<document>` *before* its path component. The resource
|
|
91
|
+
* here is `<base>/mcp/<identifier>`, so the documents are at
|
|
92
|
+
* `<base>/.well-known/oauth-protected-resource/mcp/<identifier>` — the app
|
|
93
|
+
* identifier last, not `…/oauth-protected-resource/<identifier>`, which is the
|
|
94
|
+
* spelling the design note used in prose and which no conforming client would
|
|
95
|
+
* ever fetch. Writing them here is what keeps that reading from being made
|
|
96
|
+
* twice.
|
|
97
|
+
*
|
|
98
|
+
* **These are paths, not URLs.** Append them to `PUBLIC_API_BASE_URL`, for the
|
|
99
|
+
* reason `mcpAppEndpointPath` states: the cloud mints every issuer, resource
|
|
100
|
+
* and `aud` from the canonical base and compares a token's `aud` against that
|
|
101
|
+
* string, never against the request's `Host`. The friendly alias is a proxy in
|
|
102
|
+
* front of the same cloud, and a URL built on it hands a client an audience
|
|
103
|
+
* the token endpoint will refuse.
|
|
104
|
+
*
|
|
105
|
+
* Named in the shape `OAUTH_PATHS` had, and deliberately a **function** rather
|
|
106
|
+
* than the object that constant was: there is one set of these per app, and a
|
|
107
|
+
* frozen object would have to be built at a call site that knows the
|
|
108
|
+
* identifier anyway. The lesson kept from `OAUTH_PATHS` is the other one — it
|
|
109
|
+
* stood for months with an entry naming a route the cloud had deleted — so
|
|
110
|
+
* `routes.ts` builds its manifest rows *from this function*, passing
|
|
111
|
+
* `':appIdentifier'`, and the cloud's route-manifest test holds the registered
|
|
112
|
+
* routes to the manifest. A path that stops existing cannot stay spelled here.
|
|
113
|
+
*/
|
|
114
|
+
export interface McpAppPaths {
|
|
115
|
+
/** The Streamable HTTP transport itself: `POST` carries JSON-RPC, `GET` and `DELETE` are the stateless transport's `405`. */
|
|
116
|
+
readonly endpoint: string;
|
|
117
|
+
/** RFC 9728 protected-resource metadata for the endpoint. */
|
|
118
|
+
readonly protectedResourceMetadata: string;
|
|
119
|
+
/** RFC 8414 authorization-server metadata; this app's MCP server is its own authorization server. */
|
|
120
|
+
readonly authorizationServerMetadata: string;
|
|
121
|
+
/** RFC 7591 dynamic client registration, per app. */
|
|
122
|
+
readonly register: string;
|
|
123
|
+
/** The authorization endpoint, which redirects to the app's own `mcp_login_url` rather than rendering a page. */
|
|
124
|
+
readonly authorize: string;
|
|
125
|
+
/** The token endpoint; `authorization_code` with PKCE and nothing else. */
|
|
126
|
+
readonly token: string;
|
|
127
|
+
}
|
|
128
|
+
export declare function MCP_APP_PATHS(appIdentifier: string): McpAppPaths;
|
|
129
|
+
/**
|
|
130
|
+
* Which exposed kind a tool came from. Not the MCP protocol's vocabulary —
|
|
131
|
+
* ours, so the console can group a preview the way the services editor is
|
|
132
|
+
* grouped.
|
|
133
|
+
*/
|
|
134
|
+
export declare const mcpToolKind: z.ZodEnum<{
|
|
135
|
+
datapoint: "datapoint";
|
|
136
|
+
action: "action";
|
|
137
|
+
service: "service";
|
|
138
|
+
publisher: "publisher";
|
|
139
|
+
camera: "camera";
|
|
140
|
+
}>;
|
|
141
|
+
export type McpToolKind = z.infer<typeof mcpToolKind>;
|
|
142
|
+
/** MCP's own bound on a tool name, and the charset that is safe across clients. */
|
|
143
|
+
export declare const MCP_TOOL_NAME_MAX = 128;
|
|
144
|
+
export declare const mcpToolNamePattern: RegExp;
|
|
145
|
+
/**
|
|
146
|
+
* One exposure of a robot, as `robot_describe` and the console's per-role
|
|
147
|
+
* preview list it (FL-006). Every exposure the role grants is listed —
|
|
148
|
+
* a missing `description` is shown as `null`, never used to hide the entry.
|
|
149
|
+
*
|
|
150
|
+
* `input_schema` is a JSON Schema document generated from an action's,
|
|
151
|
+
* service's or publisher's `parameters`; `null` for the other kinds. It is
|
|
152
|
+
* `unknown` for the same reason the retired `mcpToolPreview.input_schema`
|
|
153
|
+
* was: pinning it would mean maintaining a zod description of JSON Schema.
|
|
154
|
+
*/
|
|
155
|
+
export declare const mcpExposure: z.ZodObject<{
|
|
156
|
+
slug: z.ZodString;
|
|
157
|
+
kind: z.ZodEnum<{
|
|
158
|
+
datapoint: "datapoint";
|
|
159
|
+
action: "action";
|
|
160
|
+
service: "service";
|
|
161
|
+
publisher: "publisher";
|
|
162
|
+
camera: "camera";
|
|
163
|
+
}>;
|
|
164
|
+
description: z.ZodNullable<z.ZodString>;
|
|
165
|
+
unit: z.ZodNullable<z.ZodString>;
|
|
166
|
+
decimals: z.ZodNullable<z.ZodNumber>;
|
|
167
|
+
input_schema: z.ZodNullable<z.ZodUnknown>;
|
|
168
|
+
}, z.core.$strip>;
|
|
169
|
+
export type McpExposure = z.infer<typeof mcpExposure>;
|
|
170
|
+
/** The two role capabilities a robot tool can need beyond a slug grant. `presence` is a stream and has no tool. */
|
|
171
|
+
export declare const mcpCapabilities: z.ZodObject<{
|
|
172
|
+
action_history: z.ZodBoolean;
|
|
173
|
+
assets: z.ZodBoolean;
|
|
174
|
+
}, z.core.$strip>;
|
|
175
|
+
export type McpCapabilities = z.infer<typeof mcpCapabilities>;
|
|
176
|
+
/** What one caller may do on one robot — the answer to `robot_describe`. */
|
|
177
|
+
export declare const mcpRobotDatasheet: z.ZodObject<{
|
|
178
|
+
robot_id: z.ZodUUID;
|
|
179
|
+
robot_name: z.ZodString;
|
|
180
|
+
capabilities: z.ZodObject<{
|
|
181
|
+
action_history: z.ZodBoolean;
|
|
182
|
+
assets: z.ZodBoolean;
|
|
183
|
+
}, z.core.$strip>;
|
|
184
|
+
exposures: z.ZodArray<z.ZodObject<{
|
|
185
|
+
slug: z.ZodString;
|
|
186
|
+
kind: z.ZodEnum<{
|
|
187
|
+
datapoint: "datapoint";
|
|
188
|
+
action: "action";
|
|
189
|
+
service: "service";
|
|
190
|
+
publisher: "publisher";
|
|
191
|
+
camera: "camera";
|
|
192
|
+
}>;
|
|
193
|
+
description: z.ZodNullable<z.ZodString>;
|
|
194
|
+
unit: z.ZodNullable<z.ZodString>;
|
|
195
|
+
decimals: z.ZodNullable<z.ZodNumber>;
|
|
196
|
+
input_schema: z.ZodNullable<z.ZodUnknown>;
|
|
197
|
+
}, z.core.$strip>>;
|
|
198
|
+
}, z.core.$strip>;
|
|
199
|
+
export type McpRobotDatasheet = z.infer<typeof mcpRobotDatasheet>;
|
|
200
|
+
/**
|
|
201
|
+
* What a developer sees before an end user connects: the datasheet each
|
|
202
|
+
* robot of the app would answer for one role. Replaces the per-slug tool
|
|
203
|
+
* preview and its `omitted` list — with a fixed catalog there is no tool to
|
|
204
|
+
* omit, only exposures to grant.
|
|
205
|
+
*/
|
|
206
|
+
export declare const mcpRolePreviewResponse: z.ZodObject<{
|
|
207
|
+
role_id: z.ZodUUID;
|
|
208
|
+
robots: z.ZodArray<z.ZodObject<{
|
|
209
|
+
robot_id: z.ZodUUID;
|
|
210
|
+
robot_name: z.ZodString;
|
|
211
|
+
capabilities: z.ZodObject<{
|
|
212
|
+
action_history: z.ZodBoolean;
|
|
213
|
+
assets: z.ZodBoolean;
|
|
214
|
+
}, z.core.$strip>;
|
|
215
|
+
exposures: z.ZodArray<z.ZodObject<{
|
|
216
|
+
slug: z.ZodString;
|
|
217
|
+
kind: z.ZodEnum<{
|
|
218
|
+
datapoint: "datapoint";
|
|
219
|
+
action: "action";
|
|
220
|
+
service: "service";
|
|
221
|
+
publisher: "publisher";
|
|
222
|
+
camera: "camera";
|
|
223
|
+
}>;
|
|
224
|
+
description: z.ZodNullable<z.ZodString>;
|
|
225
|
+
unit: z.ZodNullable<z.ZodString>;
|
|
226
|
+
decimals: z.ZodNullable<z.ZodNumber>;
|
|
227
|
+
input_schema: z.ZodNullable<z.ZodUnknown>;
|
|
228
|
+
}, z.core.$strip>>;
|
|
229
|
+
}, z.core.$strip>>;
|
|
230
|
+
}, z.core.$strip>;
|
|
231
|
+
export type McpRolePreviewResponse = z.infer<typeof mcpRolePreviewResponse>;
|
|
232
|
+
/**
|
|
233
|
+
* Where a signed asset link is served. An MCP session token is refused on
|
|
234
|
+
* REST by design, so `asset_get`/`urdf_get` mint a bearer-free link the agent
|
|
235
|
+
* behind the client can fetch. Lifetime is fixed; the token binds robot, asset
|
|
236
|
+
* and expiry under `JWT_SECRET`.
|
|
237
|
+
*/
|
|
238
|
+
export declare const MCP_ASSET_LINK_PATH: "/api/asset-links";
|
|
239
|
+
export declare const MCP_ASSET_LINK_TTL_MS: number;
|