@fleetless/contracts 1.0.6 → 1.2.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 +12 -0
- package/artifacts/openapi.json +381 -49
- package/artifacts/routes.json +75 -4
- package/artifacts/schema/authorization-server-metadata.schema.json +1 -1
- package/artifacts/schema/client-robot-list-item.schema.json +70 -0
- package/artifacts/schema/client-robot-list-response.schema.json +83 -0
- package/artifacts/schema/dynamic-client-registration-request.schema.json +1 -1
- package/artifacts/schema/dynamic-client-registration-response.schema.json +1 -1
- package/artifacts/schema/oauth-token-request.schema.json +79 -41
- package/artifacts/schema/oauth-token-response.schema.json +1 -1
- package/dist/client-robots.d.ts +37 -0
- package/dist/client-robots.js +30 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/mcp.d.ts +6 -1
- package/dist/mcp.js +6 -1
- package/dist/oauth.d.ts +34 -19
- package/dist/oauth.js +39 -24
- package/dist/realtime.d.ts +2 -2
- package/dist/routes.js +48 -12
- package/package.json +1 -1
package/dist/oauth.d.ts
CHANGED
|
@@ -168,26 +168,20 @@ export declare const dynamicClientRegistrationResponse: z.ZodObject<{
|
|
|
168
168
|
}, z.core.$strip>;
|
|
169
169
|
export type DynamicClientRegistrationResponse = z.infer<typeof dynamicClientRegistrationResponse>;
|
|
170
170
|
/**
|
|
171
|
-
* **The MCP token endpoint's request —
|
|
172
|
-
*
|
|
171
|
+
* **The MCP token endpoint's request — two grants, one per half of a
|
|
172
|
+
* session.**
|
|
173
173
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
174
|
+
* `authorization_code` mints the first access token and a refresh token;
|
|
175
|
+
* `refresh_token` rotates that refresh token into a new pair. Both MCP
|
|
176
|
+
* authorization servers, central and per-app, answer both; the console's own
|
|
177
|
+
* OAuth portal answers the code grant only.
|
|
178
178
|
*
|
|
179
|
-
* **
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* The argument the branch carried is worth keeping even though the branch is
|
|
186
|
-
* not: **RFC 8707's `resource` has to survive rotation**, because a refresh
|
|
187
|
-
* that drops the audience mints a successor with no `aud`, and the validating
|
|
188
|
-
* resource then refuses a token the caller obtained legitimately — one token
|
|
189
|
-
* lifetime after a login that worked, to somebody who did nothing wrong. If a
|
|
190
|
-
* refresh grant is ever added here, it carries `resource`.
|
|
179
|
+
* **RFC 8707's `resource` has to survive rotation**: a refresh that drops the
|
|
180
|
+
* audience mints a successor with no `aud`, and the validating resource then
|
|
181
|
+
* refuses a token the caller obtained legitimately. So the server keeps the
|
|
182
|
+
* audience on the refresh token's own row, and a `resource` named here must
|
|
183
|
+
* match it or the answer is `invalid_target` — before the token is consumed,
|
|
184
|
+
* so a typo costs nothing.
|
|
191
185
|
*
|
|
192
186
|
* `code_verifier`'s bounds are RFC 7636 §4.1's, charset included. A verifier
|
|
193
187
|
* is compared, not parsed, so a length nobody checks is a length an attacker
|
|
@@ -201,7 +195,7 @@ export type DynamicClientRegistrationResponse = z.infer<typeof dynamicClientRegi
|
|
|
201
195
|
* **stripped**, which bit the test for this schema: `safeParse().success`
|
|
202
196
|
* cannot tell a present field from an absent one. Assert on the parsed value.
|
|
203
197
|
*/
|
|
204
|
-
export declare const
|
|
198
|
+
export declare const oauthCodeTokenRequest: z.ZodObject<{
|
|
205
199
|
grant_type: z.ZodLiteral<"authorization_code">;
|
|
206
200
|
code: z.ZodString;
|
|
207
201
|
redirect_uri: z.ZodString;
|
|
@@ -209,6 +203,27 @@ export declare const oauthTokenRequest: z.ZodObject<{
|
|
|
209
203
|
code_verifier: z.ZodString;
|
|
210
204
|
resource: z.ZodOptional<z.ZodURL>;
|
|
211
205
|
}, z.core.$strip>;
|
|
206
|
+
export type OauthCodeTokenRequest = z.infer<typeof oauthCodeTokenRequest>;
|
|
207
|
+
export declare const oauthRefreshTokenRequest: z.ZodObject<{
|
|
208
|
+
grant_type: z.ZodLiteral<"refresh_token">;
|
|
209
|
+
refresh_token: z.ZodString;
|
|
210
|
+
client_id: z.ZodString;
|
|
211
|
+
resource: z.ZodOptional<z.ZodURL>;
|
|
212
|
+
}, z.core.$strip>;
|
|
213
|
+
export type OauthRefreshTokenRequest = z.infer<typeof oauthRefreshTokenRequest>;
|
|
214
|
+
export declare const oauthTokenRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
215
|
+
grant_type: z.ZodLiteral<"authorization_code">;
|
|
216
|
+
code: z.ZodString;
|
|
217
|
+
redirect_uri: z.ZodString;
|
|
218
|
+
client_id: z.ZodString;
|
|
219
|
+
code_verifier: z.ZodString;
|
|
220
|
+
resource: z.ZodOptional<z.ZodURL>;
|
|
221
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
222
|
+
grant_type: z.ZodLiteral<"refresh_token">;
|
|
223
|
+
refresh_token: z.ZodString;
|
|
224
|
+
client_id: z.ZodString;
|
|
225
|
+
resource: z.ZodOptional<z.ZodURL>;
|
|
226
|
+
}, z.core.$strip>], "grant_type">;
|
|
212
227
|
export type OauthTokenRequest = z.infer<typeof oauthTokenRequest>;
|
|
213
228
|
/**
|
|
214
229
|
* RFC 6749 §5.1's success envelope — **the second deliberate dialect, and this
|
package/dist/oauth.js
CHANGED
|
@@ -188,7 +188,7 @@ export const dynamicClientRegistrationRequest = z
|
|
|
188
188
|
description: '`none`, RFC 7591\'s value for a public client, and the only value either server registers. Any other value is **refused rather than silently downgraded**: a client that believes it holds a secret and does not has a wrong mental model of its own security. There is no client secret to hold — mandatory PKCE (`S256`) is the defence.',
|
|
189
189
|
}),
|
|
190
190
|
grant_types: z.array(z.enum(['authorization_code', 'refresh_token'])).optional().meta({
|
|
191
|
-
description: 'Accepted for conformance with RFC 7591 and then **ignored
|
|
191
|
+
description: 'Accepted for conformance with RFC 7591 and then **ignored**: both MCP authorization servers grant `authorization_code` and `refresh_token` to every registration, and the answer states what was granted (§3.2.1) rather than what was asked.',
|
|
192
192
|
}),
|
|
193
193
|
response_types: z.array(z.enum(['code'])).optional().meta({
|
|
194
194
|
description: 'Accepted for conformance and then **ignored**; the response names `code`, which is the only response type OAuth 2.1 leaves, the implicit grant having been removed.',
|
|
@@ -211,7 +211,7 @@ export const dynamicClientRegistrationResponse = z.object({
|
|
|
211
211
|
description: 'The redirect URIs this registration was accepted for. A code is returned to one of these and nowhere else.',
|
|
212
212
|
}),
|
|
213
213
|
grant_types: z.array(z.string()).meta({
|
|
214
|
-
description: 'The grants this client may use. Always exactly `["authorization_code"]` —
|
|
214
|
+
description: 'The grants this client may use. Always exactly `["authorization_code", "refresh_token"]` — an exchange mints a refresh token and the token endpoint rotates it.',
|
|
215
215
|
}),
|
|
216
216
|
response_types: z.array(z.string()).meta({
|
|
217
217
|
description: 'The response types this client may ask for: `code`.',
|
|
@@ -227,26 +227,20 @@ export const dynamicClientRegistrationResponse = z.object({
|
|
|
227
227
|
}),
|
|
228
228
|
});
|
|
229
229
|
/**
|
|
230
|
-
* **The MCP token endpoint's request —
|
|
231
|
-
*
|
|
230
|
+
* **The MCP token endpoint's request — two grants, one per half of a
|
|
231
|
+
* session.**
|
|
232
232
|
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
233
|
+
* `authorization_code` mints the first access token and a refresh token;
|
|
234
|
+
* `refresh_token` rotates that refresh token into a new pair. Both MCP
|
|
235
|
+
* authorization servers, central and per-app, answer both; the console's own
|
|
236
|
+
* OAuth portal answers the code grant only.
|
|
237
237
|
*
|
|
238
|
-
* **
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* The argument the branch carried is worth keeping even though the branch is
|
|
245
|
-
* not: **RFC 8707's `resource` has to survive rotation**, because a refresh
|
|
246
|
-
* that drops the audience mints a successor with no `aud`, and the validating
|
|
247
|
-
* resource then refuses a token the caller obtained legitimately — one token
|
|
248
|
-
* lifetime after a login that worked, to somebody who did nothing wrong. If a
|
|
249
|
-
* refresh grant is ever added here, it carries `resource`.
|
|
238
|
+
* **RFC 8707's `resource` has to survive rotation**: a refresh that drops the
|
|
239
|
+
* audience mints a successor with no `aud`, and the validating resource then
|
|
240
|
+
* refuses a token the caller obtained legitimately. So the server keeps the
|
|
241
|
+
* audience on the refresh token's own row, and a `resource` named here must
|
|
242
|
+
* match it or the answer is `invalid_target` — before the token is consumed,
|
|
243
|
+
* so a typo costs nothing.
|
|
250
244
|
*
|
|
251
245
|
* `code_verifier`'s bounds are RFC 7636 §4.1's, charset included. A verifier
|
|
252
246
|
* is compared, not parsed, so a length nobody checks is a length an attacker
|
|
@@ -260,10 +254,10 @@ export const dynamicClientRegistrationResponse = z.object({
|
|
|
260
254
|
* **stripped**, which bit the test for this schema: `safeParse().success`
|
|
261
255
|
* cannot tell a present field from an absent one. Assert on the parsed value.
|
|
262
256
|
*/
|
|
263
|
-
export const
|
|
257
|
+
export const oauthCodeTokenRequest = z
|
|
264
258
|
.object({
|
|
265
259
|
grant_type: z.literal('authorization_code').meta({
|
|
266
|
-
description: '
|
|
260
|
+
description: '`authorization_code`: this request exchanges the code from the authorize redirect for an access token and a refresh token.',
|
|
267
261
|
}),
|
|
268
262
|
code: z.string().min(1).max(500).meta({
|
|
269
263
|
description: 'The authorization code from the redirect. It may be exchanged once; a second presentation is `invalid_grant`, the same answer a fabricated code gets.',
|
|
@@ -284,6 +278,27 @@ export const oauthTokenRequest = z
|
|
|
284
278
|
.meta({
|
|
285
279
|
description: "RFC 6749 §4.1.3's authorization-code exchange with PKCE, as either MCP authorization server reads it. Sent as `application/x-www-form-urlencoded`, per §4.1.3, though the server accepts a JSON body too.",
|
|
286
280
|
});
|
|
281
|
+
export const oauthRefreshTokenRequest = z
|
|
282
|
+
.object({
|
|
283
|
+
grant_type: z.literal('refresh_token').meta({
|
|
284
|
+
description: '`refresh_token`: this request rotates a refresh token into a new access token and a new refresh token. The presented token is consumed; presenting it again revokes the whole session.',
|
|
285
|
+
}),
|
|
286
|
+
refresh_token: z.string().min(1).max(500).meta({
|
|
287
|
+
description: 'The refresh token from the last token response. Bound to the client that received it and to one identity space: presented by another client, or at the other MCP server, it is `invalid_grant` and stays unconsumed.',
|
|
288
|
+
}),
|
|
289
|
+
client_id: z.string().min(1).max(200).meta({
|
|
290
|
+
description: 'The client the refresh token was issued to, as registered. A refresh token is not transferable between clients.',
|
|
291
|
+
}),
|
|
292
|
+
resource: z.url().optional().meta({
|
|
293
|
+
description: 'The resource the new token is for, per RFC 8707. Optional; when named it must be the audience the session was issued for, or the answer is `invalid_target` and the refresh token is left untouched. The successor carries the same audience either way.',
|
|
294
|
+
}),
|
|
295
|
+
})
|
|
296
|
+
.meta({
|
|
297
|
+
description: "RFC 6749 §6's refresh, as either MCP authorization server reads it. Every use rotates: the answer carries a new refresh token and the presented one is dead.",
|
|
298
|
+
});
|
|
299
|
+
export const oauthTokenRequest = z.discriminatedUnion('grant_type', [oauthCodeTokenRequest, oauthRefreshTokenRequest]).meta({
|
|
300
|
+
description: 'What an MCP token endpoint accepts: the authorization-code exchange, or a refresh. Any other `grant_type` is `unsupported_grant_type`, refused before a lookup happens.',
|
|
301
|
+
});
|
|
287
302
|
/**
|
|
288
303
|
* RFC 6749 §5.1's success envelope — **the second deliberate dialect, and this
|
|
289
304
|
* one is a success shape rather than an error shape.**
|
|
@@ -311,7 +326,7 @@ export const oauthTokenResponse = z.object({
|
|
|
311
326
|
description: 'How long the access token is valid, in **seconds**, per RFC 6749 §5.1. Not a timestamp, and not milliseconds.',
|
|
312
327
|
}),
|
|
313
328
|
refresh_token: z.string().min(1).optional().meta({
|
|
314
|
-
description: 'The refresh token
|
|
329
|
+
description: 'The refresh token. Both MCP token endpoints issue one on every exchange and every refresh; it rotates on every use, lives ninety days from its last use, and dies with the account\'s sessions — a block, a password change, a withdrawn consent. The console\'s own OAuth portal issues none.',
|
|
315
330
|
}),
|
|
316
331
|
scope: z.string().max(500).optional().meta({
|
|
317
332
|
description: 'The scopes the issued token actually carries, space-separated.',
|
|
@@ -335,7 +350,7 @@ export const authorizationServerMetadata = z.object({
|
|
|
335
350
|
description: 'The response types this server offers: `code` only, the implicit grant being gone with OAuth 2.1.',
|
|
336
351
|
}),
|
|
337
352
|
grant_types_supported: z.array(z.enum(['authorization_code', 'refresh_token'])).meta({
|
|
338
|
-
description: 'The grants this server offers
|
|
353
|
+
description: 'The grants this server offers: `authorization_code` and `refresh_token`. OAuth 2.1 removes the implicit and password grants, so neither appears here.',
|
|
339
354
|
}),
|
|
340
355
|
code_challenge_methods_supported: z.array(codeChallengeMethod).meta({
|
|
341
356
|
description: 'The PKCE challenge methods accepted: `S256` only. `plain` is not offered — a challenge equal to its verifier defends against nothing, and offering it would make a downgrade negotiable.',
|
package/dist/realtime.d.ts
CHANGED
|
@@ -254,8 +254,8 @@ export type DatapointEvent = z.infer<typeof datapointEvent>;
|
|
|
254
254
|
*/
|
|
255
255
|
export declare const liveSessionEndReason: z.ZodEnum<{
|
|
256
256
|
unknown: "unknown";
|
|
257
|
-
robot_offline: "robot_offline";
|
|
258
257
|
publish_failed: "publish_failed";
|
|
258
|
+
robot_offline: "robot_offline";
|
|
259
259
|
released_by_peer: "released_by_peer";
|
|
260
260
|
config_changed: "config_changed";
|
|
261
261
|
revoked: "revoked";
|
|
@@ -285,8 +285,8 @@ export declare const liveSessionEvent: z.ZodObject<{
|
|
|
285
285
|
state: z.ZodLiteral<"ended">;
|
|
286
286
|
reason: z.ZodEnum<{
|
|
287
287
|
unknown: "unknown";
|
|
288
|
-
robot_offline: "robot_offline";
|
|
289
288
|
publish_failed: "publish_failed";
|
|
289
|
+
robot_offline: "robot_offline";
|
|
290
290
|
released_by_peer: "released_by_peer";
|
|
291
291
|
config_changed: "config_changed";
|
|
292
292
|
revoked: "revoked";
|
package/dist/routes.js
CHANGED
|
@@ -4,10 +4,11 @@ import { alertListResponse, orgAlertsQuery, orgFiringAlertsResponse } from './al
|
|
|
4
4
|
import { asset, assetListResponse, assetSyncRequest, assetSyncResponse, assetSyncStatus, missingAssetQuery } from './assets.js';
|
|
5
5
|
import { auditListResponse, auditQuery } from './audit.js';
|
|
6
6
|
import { CLIENT_OIDC_CALLBACK_PATH, clientAcceptInvitationRequest, clientIdentity, clientLoginRequest, clientLogoutRequest, clientMcpInteraction, clientMcpInteractionDecisionResponse, clientOidcCallbackQuery, clientOidcExchangeRequest, clientOidcStartQuery, clientPasswordResetConfirmRequest, clientPasswordResetRequest, clientProviderListQuery, clientProviderListResponse, clientRefreshRequest, clientRegisterRequest, clientResendVerificationRequest, clientVerifyEmailRequest, mcpConsentGrantListResponse, } from './client-auth.js';
|
|
7
|
+
import { clientRobotListResponse } from './client-robots.js';
|
|
7
8
|
import { appAuthConfig, appInvitation, appInvitationListResponse, appMailTemplate, appMailTemplateListResponse, appOidcProvider, appOidcProviderListResponse, appUser, appUserListResponse, createAppInvitationRequest, createAppOidcProviderRequest, createAppUserRequest, mailOutcome, mailTemplatePreviewRequest, mailTemplatePreviewResponse, patchAppOidcProviderRequest, patchAppUserRequest, putAppAuthConfigRequest, putAppMailTemplateRequest, } from './app-users.js';
|
|
8
9
|
import { acceptTeamInviteRequest, authMeResponse, createTeamInviteRequest, fleetlessUser, fleetlessUserListResponse, passwordChangeRequest, passwordResetConfirm, passwordResetRequest, patchAuthMeRequest, patchFleetlessUserRequest, patchOrgRequest, patchOrgResponse, pendingTeamInviteListResponse, refreshRequest, sessionTokens, signUpRequest, signUpResponse, teamInvite, tierChangeRequest, waitlistRequest, } from './identity.js';
|
|
9
10
|
import { jobRunListResponse, jobRunQuery, jobRunSummary, jobRunSummaryQuery } from './jobs.js';
|
|
10
|
-
import { MCP_APP_PATHS, mcpRolePreviewResponse } from './mcp.js';
|
|
11
|
+
import { MCP_APP_PATHS, mcpRobotDatasheet, mcpRolePreviewResponse } from './mcp.js';
|
|
11
12
|
import { authorizationServerMetadata, dynamicClientRegistrationRequest, dynamicClientRegistrationResponse, oauthAuthorizeQuery, oauthRedirectResponse, oauthTokenRequest, oauthTokenResponse, protectedResourceMetadata, } from './oauth.js';
|
|
12
13
|
import { cameraListResponse, cancelRequest, configDraftResponse, configVersionResponse, configVersionsResponse, createRobotRequest, createRobotResponse, datapointListResponse, datapointValue, exposureListResponse, fetchTypesRequest, fetchTypesResponse, historyQuery, historyResponse, introspectionResponse, invokeOrServiceResponse, invokeRequest, jobResponse, liveSessionResponse, orgHealthQuery, orgLatencyQuery, orgLatencyResponse, orgQuotaUsage, orgUsageQuery, orgUsageResponse, patchRobotRequest, patchRobotResponse, publishConfigResponse, publishRequest, putConfigDraftRequest, putRobotDetailsRequest, putRobotDetailsResponse, releaseLiveQuery, renameSlugRequest, renameSlugResponse, robotDeleteQuery, resourceHealthListResponse, robotDeletionSummary, robotDetailResponse, robotJobsResponse, robotListResponse, slugUsageResponse, snapshotMetaResponse, typesResponse, } from './rest.js';
|
|
13
14
|
export const ROUTE_SECTIONS = [
|
|
@@ -208,6 +209,14 @@ export const ROUTES = [
|
|
|
208
209
|
'and splitting them here would tell a stranger which tokens ever existed. No rate limiter: the GET changes nothing, and the POST it ' +
|
|
209
210
|
'leads to is limited per IP.',
|
|
210
211
|
},
|
|
212
|
+
{
|
|
213
|
+
method: 'GET', path: '/favicon.svg', section: 'client-auth',
|
|
214
|
+
summary: 'Serves the Fleetless icon for the auth portal\'s and the MCP welcome page\'s browser tab.',
|
|
215
|
+
audience: 'internal', auth: 'none', rateLimited: false, ownerTier: false, status: 200,
|
|
216
|
+
params: [], query: null, request: null, response: null, errors: [], transport: 'http',
|
|
217
|
+
notes: 'An SVG, not JSON. Those pages carry a Content-Security-Policy that admits no `data:` image, so the icon is a file on their own ' +
|
|
218
|
+
'origin — the one source `img-src \'self\'` names. Cached for a day: the bytes change when the brand does, not per deploy.',
|
|
219
|
+
},
|
|
211
220
|
{
|
|
212
221
|
method: 'POST', path: '/api/auth/password/reset/confirm', section: 'developer-auth',
|
|
213
222
|
summary: 'Spends a reset token, sets the new password and ends every session of the account.',
|
|
@@ -939,8 +948,8 @@ export const ROUTES = [
|
|
|
939
948
|
'caller earned. The shape is deliberately **not** strict, which is the schema agreeing with §3.1 rather than a gap in it — a conforming ' +
|
|
940
949
|
'client sends `client_uri`, `logo_uri` and `software_id`, and both the schema and the server ignore them. `client_name` and ' +
|
|
941
950
|
'`redirect_uris` are the two fields read; `grant_types`, `response_types` and `scope` are accepted and ignored. What comes back is what ' +
|
|
942
|
-
'was actually granted, which §3.2.1 allows a server to substitute — this authorization server
|
|
943
|
-
'
|
|
951
|
+
'was actually granted, which §3.2.1 allows a server to substitute — this authorization server grants `authorization_code` and ' +
|
|
952
|
+
'`refresh_token` to every registration. The registration carries a TTL. Refusals ' +
|
|
944
953
|
'are `oauthError`; the rate limiter answers `apiError`.',
|
|
945
954
|
},
|
|
946
955
|
{
|
|
@@ -1014,10 +1023,11 @@ export const ROUTES = [
|
|
|
1014
1023
|
audience: 'client', auth: 'none', rateLimited: false, ownerTier: false, status: 200,
|
|
1015
1024
|
params: [], query: null, request: oauthTokenRequest, response: oauthTokenResponse,
|
|
1016
1025
|
errors: [], transport: 'http',
|
|
1017
|
-
notes: '
|
|
1018
|
-
'
|
|
1019
|
-
'
|
|
1020
|
-
'single-use, PKCE-verified, and its
|
|
1026
|
+
notes: '`authorization_code` mints an `mcp_session` access token bound to the central resource and a refresh token; `refresh_token` rotates that pair, ' +
|
|
1027
|
+
'and the presented refresh token is consumed — a second presentation revokes the session, as on `/api/auth/refresh`. The refresh token lives ninety days ' +
|
|
1028
|
+
'from its last use and is bound to the `client_id` it was issued to. A refresh re-reads the Fleetless user, so a removed account cannot refresh. ' +
|
|
1029
|
+
'Refusals are RFC 6749 §5.2\'s `oauthError`, so this route emits none of the codes in this reference. The code is single-use, PKCE-verified, and its ' +
|
|
1030
|
+
'`resource` must match the audience it was authorized for; a `resource` on a refresh must match the session\'s audience, and is checked before the token is consumed.',
|
|
1021
1031
|
},
|
|
1022
1032
|
/* ------------------------------- developer auth (the console\'s OAuth portal) */
|
|
1023
1033
|
{
|
|
@@ -1239,8 +1249,8 @@ export const ROUTES = [
|
|
|
1239
1249
|
'accepts rather than what it parses, for the reason that row gives: §3.2.2 needs two distinguishable refusals and one `safeParse` ' +
|
|
1240
1250
|
'failure offers one. `client_name` and `redirect_uris` are read; `grant_types`, `response_types` and `scope` are accepted and ignored, ' +
|
|
1241
1251
|
'and what comes back is what was actually ' +
|
|
1242
|
-
'granted, which §3.2.1 allows —
|
|
1243
|
-
'
|
|
1252
|
+
'granted, which §3.2.1 allows — this authorization server grants `authorization_code` and `refresh_token` to every registration. ' +
|
|
1253
|
+
'The registration carries a TTL. \n\n**The registration is scoped to this app.** A `client_id` minted here ' +
|
|
1244
1254
|
'authorizes at this app\'s endpoint and nowhere else, so a client registered against one app cannot walk into another\'s authorize with ' +
|
|
1245
1255
|
'it, and a developer who switches MCP off is not left with strangers\' registrations valid somewhere adjacent. \n\nRefusals are ' +
|
|
1246
1256
|
'`oauthError`; the rate limiter and `404 not_found` answer `apiError`. That `404` covers an unknown identifier **and** an app with the ' +
|
|
@@ -1279,9 +1289,8 @@ export const ROUTES = [
|
|
|
1279
1289
|
audience: 'client', auth: 'none', rateLimited: false, ownerTier: false, status: 200,
|
|
1280
1290
|
params: [APP_IDENTIFIER], query: null, request: oauthTokenRequest, response: oauthTokenResponse,
|
|
1281
1291
|
errors: [], transport: 'http',
|
|
1282
|
-
notes: '
|
|
1283
|
-
'
|
|
1284
|
-
'optional and stays empty. **The `aud` is this app\'s endpoint URL on the canonical public base**, and the code\'s `resource` must match ' +
|
|
1292
|
+
notes: '`authorization_code`, PKCE-verified and single-use, and `refresh_token`, which rotates the pair the exchange minted; the refresh token lives ninety days from its last use, is bound to its client and to this app, and a refresh re-reads the app user\'s status and their standing consent to the client, so a block or a withdrawn consent ends the session at its next refresh at the latest. ' +
|
|
1293
|
+
'**The `aud` is this app\'s endpoint URL on the canonical public base**, and the code\'s `resource` must match ' +
|
|
1285
1294
|
'it — that is the whole of what stops a token minted for one app being spent at another\'s endpoint. \n\n**Every refusal is RFC 6749 ' +
|
|
1286
1295
|
'§5.2\'s `oauthError`, so this route emits none of the codes in this reference — including the ones about the app.** An unknown ' +
|
|
1287
1296
|
'identifier and a switched-off app are `invalid_client` here, not the `404` and `403` the authorize route beside it answers. The ' +
|
|
@@ -1750,6 +1759,33 @@ export const ROUTES = [
|
|
|
1750
1759
|
'`404 unknown_datapoint` and a configured one with no sample yet is `404 no_data` — three facts a caller who is entitled to them needs ' +
|
|
1751
1760
|
'told apart. The plane built-ins (`bridge_state`, `robot_details`) answer here too, without appearing in any document.',
|
|
1752
1761
|
},
|
|
1762
|
+
/* ------------------------------------------ discovery: the REST twins of the two MCP tools a session starts from */
|
|
1763
|
+
{
|
|
1764
|
+
method: 'GET', path: '/api/client/robots', section: 'robots',
|
|
1765
|
+
summary: 'Lists the robots the caller reaches, with bridge state and the published configuration version.',
|
|
1766
|
+
audience: 'client', auth: 'developer_or_client', rateLimited: false, ownerTier: false, status: 200,
|
|
1767
|
+
params: [], query: null, request: null, response: clientRobotListResponse,
|
|
1768
|
+
errors: [...CLIENT_GUARD], transport: 'http',
|
|
1769
|
+
notes: '**The REST twin of the MCP tool `robots_list`**, and the one robot question no robot-scoped route can answer: which robots may I name ' +
|
|
1770
|
+
'at all. An app user sees the robots their app attaches on which their role grants at least one slug or capability; a server key sees ' +
|
|
1771
|
+
'every robot its app attaches; a developer bearer sees the organisation\'s robots. Name order, id as the tiebreak. A robot on which the ' +
|
|
1772
|
+
'role grants nothing is absent rather than listed empty — the same answer `robots_list` gives, for the same reason: reach is a grant, ' +
|
|
1773
|
+
'not an attachment. Under `/api/client/` because it names no robot; every robot-scoped read stays under `/api/robots/:id/…`.',
|
|
1774
|
+
},
|
|
1775
|
+
{
|
|
1776
|
+
method: 'GET', path: '/api/robots/:id/datasheet', section: 'robots',
|
|
1777
|
+
summary: 'Describes everything the caller\'s role lets them do on one robot, with parameter schemas.',
|
|
1778
|
+
audience: 'client', auth: 'developer_or_client', rateLimited: false, ownerTier: false, status: 200,
|
|
1779
|
+
params: [{ name: 'id', description: 'The robot\'s uuid, as `GET /api/client/robots` lists it.' }],
|
|
1780
|
+
query: null, request: null, response: mcpRobotDatasheet,
|
|
1781
|
+
errors: [...CLIENT_GUARD, 'invalid_uuid', 'not_found'], transport: 'http',
|
|
1782
|
+
notes: '**The REST twin of the MCP tool `robot_describe`**: one answer per robot — every datapoint, action, service, publisher and camera the ' +
|
|
1783
|
+
'role grants, each with its `input_schema` where it takes parameters, plus the two capabilities that gate whole features, ' +
|
|
1784
|
+
'`action_history` and `assets`. A robot with nothing published answers an empty `exposures` list, never a refusal. A robot the caller ' +
|
|
1785
|
+
'does not reach — not attached to their app, or attached with a role that grants nothing on it — answers `404` exactly as one that ' +
|
|
1786
|
+
'does not exist. The app-user datapoint and camera listings under this prefix stay; this is the one read that also names actions, ' +
|
|
1787
|
+
'services, publishers and capabilities, which is what an app needs before it can draw a screen.',
|
|
1788
|
+
},
|
|
1753
1789
|
/* ------------------------------------------------- config (draft/publish) */
|
|
1754
1790
|
{
|
|
1755
1791
|
method: 'GET', path: '/api/robots/:id/config/draft', section: 'config',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetless/contracts",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Fleetless wire contracts: the bridge-cloud protocol, the REST API schemas and the error codes, as zod schemas with generated JSON Schema and OpenAPI artifacts.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Dehne Robotik GmbH",
|