@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.
Files changed (287) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +17 -0
  4. package/README.md +88 -0
  5. package/artifacts/constants.json +24 -0
  6. package/artifacts/openapi.json +17219 -0
  7. package/artifacts/routes.json +4605 -0
  8. package/artifacts/schema/accept-team-invite-request.schema.json +22 -0
  9. package/artifacts/schema/action-config.schema.json +198 -0
  10. package/artifacts/schema/alert-list-response.schema.json +172 -0
  11. package/artifacts/schema/api-error.schema.json +20 -0
  12. package/artifacts/schema/app-auth-config.schema.json +106 -0
  13. package/artifacts/schema/app-invitation-list-response.schema.json +57 -0
  14. package/artifacts/schema/app-invitation.schema.json +69 -0
  15. package/artifacts/schema/app-list-response.schema.json +82 -0
  16. package/artifacts/schema/app-mail-template-list-response.schema.json +68 -0
  17. package/artifacts/schema/app-mail-template.schema.json +54 -0
  18. package/artifacts/schema/app-oidc-provider-list-response.schema.json +93 -0
  19. package/artifacts/schema/app-oidc-provider.schema.json +80 -0
  20. package/artifacts/schema/app-user-list-response.schema.json +111 -0
  21. package/artifacts/schema/app-user.schema.json +98 -0
  22. package/artifacts/schema/app.schema.json +69 -0
  23. package/artifacts/schema/apply-error.schema.json +41 -0
  24. package/artifacts/schema/asset-list-response.schema.json +288 -0
  25. package/artifacts/schema/asset-sync-request.schema.json +17 -0
  26. package/artifacts/schema/asset-sync-response.schema.json +16 -0
  27. package/artifacts/schema/asset-sync-status.schema.json +136 -0
  28. package/artifacts/schema/asset.schema.json +68 -0
  29. package/artifacts/schema/audit-actor.schema.json +32 -0
  30. package/artifacts/schema/audit-event.schema.json +119 -0
  31. package/artifacts/schema/audit-list-response.schema.json +144 -0
  32. package/artifacts/schema/audit-query.schema.json +79 -0
  33. package/artifacts/schema/auth-error.schema.json +23 -0
  34. package/artifacts/schema/auth-me-response.schema.json +99 -0
  35. package/artifacts/schema/auth-ok.schema.json +115 -0
  36. package/artifacts/schema/authorization-server-metadata.schema.json +80 -0
  37. package/artifacts/schema/bridge-asset-progress.schema.json +99 -0
  38. package/artifacts/schema/bridge-assets-available.schema.json +25 -0
  39. package/artifacts/schema/bridge-camera-state.schema.json +78 -0
  40. package/artifacts/schema/bridge-config-applied.schema.json +67 -0
  41. package/artifacts/schema/bridge-hello.schema.json +65 -0
  42. package/artifacts/schema/bridge-introspect.schema.json +114 -0
  43. package/artifacts/schema/bridge-job-lost.schema.json +22 -0
  44. package/artifacts/schema/bridge-job-update.schema.json +100 -0
  45. package/artifacts/schema/bridge-pong.schema.json +19 -0
  46. package/artifacts/schema/bridge-pressure.schema.json +292 -0
  47. package/artifacts/schema/bridge-state.schema.json +24 -0
  48. package/artifacts/schema/bridge-type-definitions.schema.json +169 -0
  49. package/artifacts/schema/busy-details.schema.json +115 -0
  50. package/artifacts/schema/camera-descriptor.schema.json +45 -0
  51. package/artifacts/schema/camera-list-response.schema.json +58 -0
  52. package/artifacts/schema/camera-source.schema.json +240 -0
  53. package/artifacts/schema/cancel-request.schema.json +20 -0
  54. package/artifacts/schema/client-accept-invitation-request.schema.json +35 -0
  55. package/artifacts/schema/client-auth.schema.json +18 -0
  56. package/artifacts/schema/client-cancel.schema.json +45 -0
  57. package/artifacts/schema/client-identity.schema.json +103 -0
  58. package/artifacts/schema/client-invoke.schema.json +45 -0
  59. package/artifacts/schema/client-login-request.schema.json +29 -0
  60. package/artifacts/schema/client-logout-request.schema.json +14 -0
  61. package/artifacts/schema/client-mcp-interaction-decision-response.schema.json +15 -0
  62. package/artifacts/schema/client-mcp-interaction.schema.json +59 -0
  63. package/artifacts/schema/client-oidc-callback-query.schema.json +28 -0
  64. package/artifacts/schema/client-oidc-exchange-request.schema.json +21 -0
  65. package/artifacts/schema/client-oidc-start-query.schema.json +36 -0
  66. package/artifacts/schema/client-password-reset-confirm-request.schema.json +22 -0
  67. package/artifacts/schema/client-password-reset-request.schema.json +24 -0
  68. package/artifacts/schema/client-provider-list-query.schema.json +17 -0
  69. package/artifacts/schema/client-provider-list-response.schema.json +34 -0
  70. package/artifacts/schema/client-publish.schema.json +40 -0
  71. package/artifacts/schema/client-refresh-request.schema.json +14 -0
  72. package/artifacts/schema/client-register-request.schema.json +44 -0
  73. package/artifacts/schema/client-resend-verification-request.schema.json +24 -0
  74. package/artifacts/schema/client-subscribe.schema.json +43 -0
  75. package/artifacts/schema/client-unsubscribe.schema.json +26 -0
  76. package/artifacts/schema/client-verify-email-request.schema.json +15 -0
  77. package/artifacts/schema/cloud-asset-request.schema.json +37 -0
  78. package/artifacts/schema/cloud-camera-start.schema.json +41 -0
  79. package/artifacts/schema/cloud-camera-stop.schema.json +26 -0
  80. package/artifacts/schema/cloud-cancel.schema.json +33 -0
  81. package/artifacts/schema/cloud-config.schema.json +1635 -0
  82. package/artifacts/schema/cloud-hello-error.schema.json +23 -0
  83. package/artifacts/schema/cloud-hello-ok.schema.json +19 -0
  84. package/artifacts/schema/cloud-introspect-request.schema.json +19 -0
  85. package/artifacts/schema/cloud-invoke.schema.json +40 -0
  86. package/artifacts/schema/cloud-ping.schema.json +19 -0
  87. package/artifacts/schema/cloud-publish.schema.json +28 -0
  88. package/artifacts/schema/cloud-type-request.schema.json +30 -0
  89. package/artifacts/schema/command-result.schema.json +175 -0
  90. package/artifacts/schema/config-draft-response.schema.json +1695 -0
  91. package/artifacts/schema/config-state.schema.json +124 -0
  92. package/artifacts/schema/config-version-response.schema.json +1641 -0
  93. package/artifacts/schema/config-versions-response.schema.json +33 -0
  94. package/artifacts/schema/create-app-invitation-request.schema.json +40 -0
  95. package/artifacts/schema/create-app-oidc-provider-request.schema.json +70 -0
  96. package/artifacts/schema/create-app-request.schema.json +30 -0
  97. package/artifacts/schema/create-app-user-request.schema.json +42 -0
  98. package/artifacts/schema/create-robot-request.schema.json +14 -0
  99. package/artifacts/schema/create-robot-response.schema.json +44 -0
  100. package/artifacts/schema/create-server-key-response.schema.json +65 -0
  101. package/artifacts/schema/create-team-invite-request.schema.json +43 -0
  102. package/artifacts/schema/datapoint-alert-row.schema.json +160 -0
  103. package/artifacts/schema/datapoint-config.schema.json +366 -0
  104. package/artifacts/schema/datapoint-display.schema.json +31 -0
  105. package/artifacts/schema/datapoint-event.schema.json +34 -0
  106. package/artifacts/schema/datapoint-frame.schema.json +28 -0
  107. package/artifacts/schema/datapoint-list-response.schema.json +61 -0
  108. package/artifacts/schema/datapoint-value.schema.json +28 -0
  109. package/artifacts/schema/developer-login-request.schema.json +19 -0
  110. package/artifacts/schema/dynamic-client-registration-request.schema.json +60 -0
  111. package/artifacts/schema/dynamic-client-registration-response.schema.json +68 -0
  112. package/artifacts/schema/error-frame.schema.json +23 -0
  113. package/artifacts/schema/exposure-counts.schema.json +39 -0
  114. package/artifacts/schema/exposure-list-response.schema.json +43 -0
  115. package/artifacts/schema/fetch-types-request.schema.json +19 -0
  116. package/artifacts/schema/fetch-types-response.schema.json +163 -0
  117. package/artifacts/schema/fleetless-user-list-response.schema.json +73 -0
  118. package/artifacts/schema/fleetless-user.schema.json +60 -0
  119. package/artifacts/schema/history-buckets-response.schema.json +79 -0
  120. package/artifacts/schema/history-query.schema.json +58 -0
  121. package/artifacts/schema/history-response.schema.json +150 -0
  122. package/artifacts/schema/history-samples-response.schema.json +68 -0
  123. package/artifacts/schema/introspection-response.schema.json +118 -0
  124. package/artifacts/schema/invoke-or-service-response.schema.json +141 -0
  125. package/artifacts/schema/invoke-request.schema.json +23 -0
  126. package/artifacts/schema/invoke-response.schema.json +125 -0
  127. package/artifacts/schema/job-actor.schema.json +34 -0
  128. package/artifacts/schema/job-event.schema.json +158 -0
  129. package/artifacts/schema/job-response.schema.json +123 -0
  130. package/artifacts/schema/job-run-list-response.schema.json +222 -0
  131. package/artifacts/schema/job-run-query.schema.json +95 -0
  132. package/artifacts/schema/job-run-summary-query.schema.json +23 -0
  133. package/artifacts/schema/job-run-summary.schema.json +33 -0
  134. package/artifacts/schema/job-run.schema.json +195 -0
  135. package/artifacts/schema/job-state.schema.json +11 -0
  136. package/artifacts/schema/job.schema.json +106 -0
  137. package/artifacts/schema/latency-bucket.schema.json +63 -0
  138. package/artifacts/schema/live-session-response.schema.json +41 -0
  139. package/artifacts/schema/mail-outcome.schema.json +20 -0
  140. package/artifacts/schema/mail-template-preview-request.schema.json +35 -0
  141. package/artifacts/schema/mail-template-preview-response.schema.json +31 -0
  142. package/artifacts/schema/mail-template-problem-details.schema.json +24 -0
  143. package/artifacts/schema/mcp-consent-grant-list-response.schema.json +52 -0
  144. package/artifacts/schema/mcp-consent-grant.schema.json +39 -0
  145. package/artifacts/schema/mcp-robot-datasheet.schema.json +115 -0
  146. package/artifacts/schema/mcp-role-preview-response.schema.json +134 -0
  147. package/artifacts/schema/missing-asset-query.schema.json +11 -0
  148. package/artifacts/schema/oauth-authorize-query.schema.json +47 -0
  149. package/artifacts/schema/oauth-redirect-response.schema.json +15 -0
  150. package/artifacts/schema/oauth-token-request.schema.json +47 -0
  151. package/artifacts/schema/oauth-token-response.schema.json +38 -0
  152. package/artifacts/schema/org-alerts-query.schema.json +15 -0
  153. package/artifacts/schema/org-event-dropped.schema.json +26 -0
  154. package/artifacts/schema/org-event-replay.schema.json +97 -0
  155. package/artifacts/schema/org-event-subscribe.schema.json +14 -0
  156. package/artifacts/schema/org-event-unsubscribe.schema.json +14 -0
  157. package/artifacts/schema/org-event.schema.json +75 -0
  158. package/artifacts/schema/org-firing-alerts-response.schema.json +178 -0
  159. package/artifacts/schema/org-health-query.schema.json +13 -0
  160. package/artifacts/schema/org-latency-query.schema.json +42 -0
  161. package/artifacts/schema/org-latency-response.schema.json +124 -0
  162. package/artifacts/schema/org-quota-usage-counts.schema.json +42 -0
  163. package/artifacts/schema/org-quota-usage.schema.json +102 -0
  164. package/artifacts/schema/org-quotas.schema.json +51 -0
  165. package/artifacts/schema/org-usage-query.schema.json +19 -0
  166. package/artifacts/schema/org-usage-response.schema.json +77 -0
  167. package/artifacts/schema/org.schema.json +30 -0
  168. package/artifacts/schema/parameter-invalid-details.schema.json +37 -0
  169. package/artifacts/schema/parameter-spec.schema.json +120 -0
  170. package/artifacts/schema/parameter-violation.schema.json +24 -0
  171. package/artifacts/schema/password-change-request.schema.json +21 -0
  172. package/artifacts/schema/password-reset-confirm.schema.json +19 -0
  173. package/artifacts/schema/password-reset-request.schema.json +14 -0
  174. package/artifacts/schema/patch-app-oidc-provider-request.schema.json +50 -0
  175. package/artifacts/schema/patch-app-user-request.schema.json +34 -0
  176. package/artifacts/schema/patch-auth-me-request.schema.json +22 -0
  177. package/artifacts/schema/patch-fleetless-user-request.schema.json +20 -0
  178. package/artifacts/schema/patch-org-request.schema.json +15 -0
  179. package/artifacts/schema/patch-org-response.schema.json +40 -0
  180. package/artifacts/schema/patch-robot-request.schema.json +15 -0
  181. package/artifacts/schema/patch-robot-response.schema.json +40 -0
  182. package/artifacts/schema/pending-team-invite-list-response.schema.json +52 -0
  183. package/artifacts/schema/pending-team-invite.schema.json +39 -0
  184. package/artifacts/schema/protected-resource-metadata.schema.json +41 -0
  185. package/artifacts/schema/publish-config-response.schema.json +21 -0
  186. package/artifacts/schema/publish-request.schema.json +17 -0
  187. package/artifacts/schema/publisher-config.schema.json +285 -0
  188. package/artifacts/schema/put-app-auth-config-request.schema.json +93 -0
  189. package/artifacts/schema/put-app-mail-template-request.schema.json +35 -0
  190. package/artifacts/schema/put-config-draft-request.schema.json +13 -0
  191. package/artifacts/schema/put-datapoint-display-request.schema.json +31 -0
  192. package/artifacts/schema/put-robot-details-request.schema.json +41 -0
  193. package/artifacts/schema/put-robot-details-response.schema.json +43 -0
  194. package/artifacts/schema/rate-limit-details.schema.json +15 -0
  195. package/artifacts/schema/refresh-request.schema.json +13 -0
  196. package/artifacts/schema/release-live-query.schema.json +13 -0
  197. package/artifacts/schema/rename-slug-request.schema.json +23 -0
  198. package/artifacts/schema/rename-slug-response.schema.json +24 -0
  199. package/artifacts/schema/resource-health-event.schema.json +72 -0
  200. package/artifacts/schema/resource-health-list-response.schema.json +80 -0
  201. package/artifacts/schema/resource-health-state.schema.json +68 -0
  202. package/artifacts/schema/robot-config-doc.schema.json +1616 -0
  203. package/artifacts/schema/robot-delete-query.schema.json +12 -0
  204. package/artifacts/schema/robot-deletion-summary.schema.json +63 -0
  205. package/artifacts/schema/robot-detail-response.schema.json +262 -0
  206. package/artifacts/schema/robot-details-doc.schema.json +33 -0
  207. package/artifacts/schema/robot-jobs-response.schema.json +119 -0
  208. package/artifacts/schema/robot-latency-series.schema.json +81 -0
  209. package/artifacts/schema/robot-list-item.schema.json +94 -0
  210. package/artifacts/schema/robot-list-response.schema.json +106 -0
  211. package/artifacts/schema/robot.schema.json +30 -0
  212. package/artifacts/schema/role-list-response.schema.json +48 -0
  213. package/artifacts/schema/role-permissions.schema.json +61 -0
  214. package/artifacts/schema/role.schema.json +35 -0
  215. package/artifacts/schema/ros-graph.schema.json +99 -0
  216. package/artifacts/schema/server-key-list-response.schema.json +64 -0
  217. package/artifacts/schema/server-key.schema.json +51 -0
  218. package/artifacts/schema/service-call-response.schema.json +13 -0
  219. package/artifacts/schema/service-config.schema.json +198 -0
  220. package/artifacts/schema/session-tokens.schema.json +28 -0
  221. package/artifacts/schema/sign-up-request.schema.json +26 -0
  222. package/artifacts/schema/sign-up-response.schema.json +127 -0
  223. package/artifacts/schema/slug-usage-response.schema.json +32 -0
  224. package/artifacts/schema/snapshot-header.schema.json +44 -0
  225. package/artifacts/schema/snapshot-meta-response.schema.json +85 -0
  226. package/artifacts/schema/subscribe-error.schema.json +31 -0
  227. package/artifacts/schema/team-invite.schema.json +57 -0
  228. package/artifacts/schema/tier-change-request.schema.json +17 -0
  229. package/artifacts/schema/type-definition.schema.json +144 -0
  230. package/artifacts/schema/types-response.schema.json +156 -0
  231. package/artifacts/schema/update-app-request.schema.json +32 -0
  232. package/artifacts/schema/urdf-completeness.schema.json +50 -0
  233. package/artifacts/schema/validation-issue.schema.json +43 -0
  234. package/artifacts/schema/waitlist-request.schema.json +15 -0
  235. package/artifacts/schema-outgoing/bridge-asset-progress.schema.json +102 -0
  236. package/artifacts/schema-outgoing/bridge-assets-available.schema.json +26 -0
  237. package/artifacts/schema-outgoing/bridge-camera-state.schema.json +80 -0
  238. package/artifacts/schema-outgoing/bridge-config-applied.schema.json +69 -0
  239. package/artifacts/schema-outgoing/bridge-hello.schema.json +68 -0
  240. package/artifacts/schema-outgoing/bridge-introspect.schema.json +119 -0
  241. package/artifacts/schema-outgoing/bridge-job-lost.schema.json +23 -0
  242. package/artifacts/schema-outgoing/bridge-job-update.schema.json +102 -0
  243. package/artifacts/schema-outgoing/bridge-pong.schema.json +20 -0
  244. package/artifacts/schema-outgoing/bridge-type-definitions.schema.json +174 -0
  245. package/artifacts/schema-outgoing/datapoint-frame.schema.json +29 -0
  246. package/artifacts/schema-outgoing/snapshot-header.schema.json +45 -0
  247. package/dist/alerts.d.ts +255 -0
  248. package/dist/alerts.js +193 -0
  249. package/dist/app-users.d.ts +606 -0
  250. package/dist/app-users.js +696 -0
  251. package/dist/apps.d.ts +175 -0
  252. package/dist/apps.js +267 -0
  253. package/dist/assets.d.ts +434 -0
  254. package/dist/assets.js +546 -0
  255. package/dist/audit.d.ts +129 -0
  256. package/dist/audit.js +238 -0
  257. package/dist/client-auth.d.ts +409 -0
  258. package/dist/client-auth.js +487 -0
  259. package/dist/common.d.ts +186 -0
  260. package/dist/common.js +199 -0
  261. package/dist/config-issues.d.ts +175 -0
  262. package/dist/config-issues.js +339 -0
  263. package/dist/config.d.ts +862 -0
  264. package/dist/config.js +1988 -0
  265. package/dist/errors.d.ts +52 -0
  266. package/dist/errors.js +786 -0
  267. package/dist/identity.d.ts +549 -0
  268. package/dist/identity.js +503 -0
  269. package/dist/index.d.ts +51 -0
  270. package/dist/index.js +51 -0
  271. package/dist/introspection.d.ts +99 -0
  272. package/dist/introspection.js +97 -0
  273. package/dist/jobs.d.ts +334 -0
  274. package/dist/jobs.js +345 -0
  275. package/dist/mcp.d.ts +239 -0
  276. package/dist/mcp.js +153 -0
  277. package/dist/oauth.d.ts +344 -0
  278. package/dist/oauth.js +488 -0
  279. package/dist/protocol.d.ts +781 -0
  280. package/dist/protocol.js +715 -0
  281. package/dist/realtime.d.ts +494 -0
  282. package/dist/realtime.js +512 -0
  283. package/dist/rest.d.ts +1989 -0
  284. package/dist/rest.js +1963 -0
  285. package/dist/routes.d.ts +94 -0
  286. package/dist/routes.js +2298 -0
  287. 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;