@fleetless/contracts 1.0.2 → 1.0.4

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/dist/mcp.js CHANGED
@@ -37,7 +37,7 @@ export const MCP_PROTOCOL_VERSION = '2025-11-25';
37
37
  *
38
38
  * **Not parameterised, and that is now a statement rather than the absence of
39
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
40
+ * family; an app's users reach a different endpoint, whose
41
41
  * path `mcpAppEndpointPath` builds. Two constants for two audiences, so a call
42
42
  * site says which it means instead of an argument deciding it.
43
43
  *
@@ -49,7 +49,7 @@ export const MCP_PROTOCOL_VERSION = '2025-11-25';
49
49
  */
50
50
  export const MCP_ENDPOINT_PATH = '/mcp';
51
51
  /**
52
- * The path of **one app's** MCP server (D7) — what an app user pastes into
52
+ * The path of **one app's** MCP server — what an app user pastes into
53
53
  * their AI tool, served only while the app's `appAuthConfig.mcp_enabled` is on.
54
54
  *
55
55
  * A helper rather than a template literal at four call sites, for
package/dist/oauth.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  import { z } from 'zod';
3
3
  /**
4
- * **OAuth 2.1, and it remains only for MCP** (2026-09-05 app-user-auth, D8).
4
+ * **OAuth 2.1, and it remains only for MCP.**
5
5
  *
6
6
  * This file used to describe two front doors: an app's end users signing in
7
7
  * through a Fleetless-hosted, app-branded login page, and MCP clients signing
@@ -172,10 +172,9 @@ export type DynamicClientRegistrationResponse = z.infer<typeof dynamicClientRegi
172
172
  * **The MCP token endpoint's request — one grant, because the servers serve
173
173
  * one.**
174
174
  *
175
- * Both authorization servers, central and per-app, exchange through
176
- * `exchangeMcpAuthorizationCode` (`cloud/src/mcp-oauth-core.ts`), whose first
177
- * act is to refuse anything but `authorization_code` before a single lookup
178
- * happens. There is no refresh grant here: a session ends when its token
175
+ * Both authorization servers, central and per-app, exchange through one
176
+ * implementation, whose first act is to refuse anything but
177
+ * `authorization_code` before a single lookup happens. There is no refresh grant here: a session ends when its token
179
178
  * expires and the client signs in again.
180
179
  *
181
180
  * **This was a `discriminatedUnion` with a `refresh_token` branch, and that
@@ -302,7 +301,7 @@ export type OauthRedirectResponse = z.infer<typeof oauthRedirectResponse>;
302
301
  * shape and the documentation, not the error path.
303
302
  *
304
303
  * **Four route entries point at it**: `GET /mcp/oauth/authorize` and
305
- * `GET /mcp/:appIdentifier/oauth/authorize` (D7), which read the same wire.
304
+ * `GET /mcp/:appIdentifier/oauth/authorize`, which read the same wire.
306
305
  * They spent a release naming nothing — the app-level `/oauth/authorize` this
307
306
  * was written for was deleted, and `query: null` was read as "there is no
308
307
  * query here" rather than as "the handler reads it by hand" — and the eight
@@ -325,10 +324,10 @@ export type OauthAuthorizeQuery = z.infer<typeof oauthAuthorizeQuery>;
325
324
  *
326
325
  * It carried one parameter, `app_identifier`, on the argument that RFC 7591's
327
326
  * registration body has no field for it and one endpoint could serve every
328
- * app. It was kept — explicitly, in its own doc comment — "for the per-app MCP
329
- * registration the MCP train adds (D7), which needs exactly this parameter".
327
+ * app. It was kept — explicitly, in its own doc comment — for a per-app MCP
328
+ * registration that was said to need exactly this parameter.
330
329
  *
331
- * **That train shipped and needed no such parameter.** `POST
330
+ * **That registration shipped and needed no such parameter.** `POST
332
331
  * /mcp/:appIdentifier/oauth/register` puts the app in the **path**, built by
333
332
  * `MCP_APP_PATHS`, and `resolveAppMcpTarget` reads it from `request.params`;
334
333
  * `registerMcpDynamicClient` never looks at a query at all. So the one reason
package/dist/oauth.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  import { z } from 'zod';
3
3
  /**
4
- * **OAuth 2.1, and it remains only for MCP** (2026-09-05 app-user-auth, D8).
4
+ * **OAuth 2.1, and it remains only for MCP.**
5
5
  *
6
6
  * This file used to describe two front doors: an app's end users signing in
7
7
  * through a Fleetless-hosted, app-branded login page, and MCP clients signing
@@ -77,8 +77,8 @@ export const oauthError = z.object({
77
77
  * makes the two indistinguishable to the caller, and *a field that cannot
78
78
  * express a distinction produces a workaround somewhere else*. Answering in
79
79
  * `apiError` instead would keep the distinction and hand an RFC-compliant
80
- * client a body it cannot parse — which is the conformance this wave exists
81
- * to provide.
80
+ * client a body it cannot parse, which is the conformance this dialect
81
+ * exists to provide.
82
82
  *
83
83
  * So both: `error` is what a standard client reads, `fleetless_code` is what
84
84
  * our own tooling switches on. RFC 6749 §5.2 permits additional members, and
@@ -107,8 +107,7 @@ export const redirectUri = z
107
107
  .refine((v) => {
108
108
  // Parsed, not prefix-matched. `startsWith('https://')` alone accepts the
109
109
  // literal string `https://` and anything else that merely opens with
110
- // those characters — a shape check standing in for a value check, which
111
- // is the failure this project keeps meeting under other names.
110
+ // those characters — a shape check standing in for a value check.
112
111
  let url;
113
112
  try {
114
113
  url = new URL(v);
@@ -232,10 +231,9 @@ export const dynamicClientRegistrationResponse = z.object({
232
231
  * **The MCP token endpoint's request — one grant, because the servers serve
233
232
  * one.**
234
233
  *
235
- * Both authorization servers, central and per-app, exchange through
236
- * `exchangeMcpAuthorizationCode` (`cloud/src/mcp-oauth-core.ts`), whose first
237
- * act is to refuse anything but `authorization_code` before a single lookup
238
- * happens. There is no refresh grant here: a session ends when its token
234
+ * Both authorization servers, central and per-app, exchange through one
235
+ * implementation, whose first act is to refuse anything but
236
+ * `authorization_code` before a single lookup happens. There is no refresh grant here: a session ends when its token
239
237
  * expires and the client signs in again.
240
238
  *
241
239
  * **This was a `discriminatedUnion` with a `refresh_token` branch, and that
@@ -398,14 +396,13 @@ export const oauthRedirectResponse = z.object({
398
396
  * differently, and it worked — but every path it held belonged to the app-level
399
397
  * OAuth flow (`authorize`, `token`, `register`, `consent`, `login`,
400
398
  * `impersonate`, `idpCallback`) or to the stub resource's metadata documents,
401
- * and OAuth 2.1 now remains only for MCP (D8). The MCP authorization server
402
- * builds its own paths in `cloud/src/routes/mcp-oauth.ts`, where they are read
403
- * by one file rather than by two repositories.
399
+ * and OAuth 2.1 now remains only for MCP. The MCP authorization server builds
400
+ * its own paths, where they are read by one file rather than by two.
404
401
  *
405
- * Deleted rather than left with the four entries whose routes this train also
406
- * removes, because that is precisely the defect this constant was created after
407
- * and then reproduced: its `idpStart` entry named a route the cloud had deleted
408
- * and stood for months with nothing noticing. A constant whose every value
402
+ * Deleted rather than left holding entries whose routes are gone, which is the
403
+ * defect it was created to prevent and then reproduced: an entry naming a route
404
+ * the server had deleted stood for months with nothing noticing. A constant
405
+ * whose every value
409
406
  * names a deleted route is that failure at full size.
410
407
  */
411
408
  /**
@@ -421,7 +418,7 @@ export const oauthRedirectResponse = z.object({
421
418
  * shape and the documentation, not the error path.
422
419
  *
423
420
  * **Four route entries point at it**: `GET /mcp/oauth/authorize` and
424
- * `GET /mcp/:appIdentifier/oauth/authorize` (D7), which read the same wire.
421
+ * `GET /mcp/:appIdentifier/oauth/authorize`, which read the same wire.
425
422
  * They spent a release naming nothing — the app-level `/oauth/authorize` this
426
423
  * was written for was deleted, and `query: null` was read as "there is no
427
424
  * query here" rather than as "the handler reads it by hand" — and the eight
@@ -455,10 +452,9 @@ export const oauthAuthorizeQuery = z
455
452
  // **No `scope`, because this authorization server issues none.** The field
456
453
  // was here describing itself as "carried onto the interaction and read
457
454
  // again at consent"; neither authorize handler reads it, the interaction
458
- // row has no column for it, and the consent screen answers `scopes: []`
459
- // from a comment that says so in as many words
460
- // (`cloud/src/routes/client-mcp-interactions.ts`). A parameter documented
461
- // as carried and in fact dropped is worse than one that is absent.
455
+ // row has no column for it, and the consent screen answers `scopes: []`.
456
+ // A parameter documented as carried and in fact dropped is worse than one
457
+ // that is absent.
462
458
  })
463
459
  .meta({
464
460
  description: 'The authorization request an MCP client sends, per RFC 6749 §4.1.1 with mandatory PKCE. The handler reads it parameter by parameter rather than through one parse, because the answers differ: `client_id` and `redirect_uri` are refused flat, with no redirect, since until both are confirmed there is no trusted target to bounce a browser to, and everything after them is reported to the client\'s own callback as query parameters.',
@@ -468,10 +464,10 @@ export const oauthAuthorizeQuery = z
468
464
  *
469
465
  * It carried one parameter, `app_identifier`, on the argument that RFC 7591's
470
466
  * registration body has no field for it and one endpoint could serve every
471
- * app. It was kept — explicitly, in its own doc comment — "for the per-app MCP
472
- * registration the MCP train adds (D7), which needs exactly this parameter".
467
+ * app. It was kept — explicitly, in its own doc comment — for a per-app MCP
468
+ * registration that was said to need exactly this parameter.
473
469
  *
474
- * **That train shipped and needed no such parameter.** `POST
470
+ * **That registration shipped and needed no such parameter.** `POST
475
471
  * /mcp/:appIdentifier/oauth/register` puts the app in the **path**, built by
476
472
  * `MCP_APP_PATHS`, and `resolveAppMcpTarget` reads it from `request.params`;
477
473
  * `registerMcpDynamicClient` never looks at a query at all. So the one reason
package/dist/realtime.js CHANGED
@@ -295,7 +295,7 @@ export const liveSessionEndReason = z.enum([
295
295
  /**
296
296
  * The cloud ended it and cannot say which of the above applied. **Kept
297
297
  * deliberately**: a channel that cannot say "I do not know" will say
298
- * something false instead, and this project has paid for that four times in
298
+ * something false instead, which is the costlier failure in
299
299
  * the camera path alone.
300
300
  */
301
301
  'unknown',
package/dist/rest.d.ts CHANGED
@@ -829,7 +829,7 @@ export type ServiceCallResponse = z.infer<typeof serviceCallResponse>;
829
829
  * **This union exists so the route can name a response at all.** The entry
830
830
  * carried `response: null` while the handler demonstrably answers something,
831
831
  * which reads in the generated reference as *this route returns nothing* —
832
- * the documented absence this project keeps paying for. A `null` there should
832
+ * a documented absence. A `null` there should
833
833
  * mean `204`, and on this route it did not.
834
834
  */
835
835
  export declare const invokeOrServiceResponse: z.ZodUnion<readonly [z.ZodObject<{
package/dist/rest.js CHANGED
@@ -416,7 +416,7 @@ export const serviceCallResponse = z.object({
416
416
  * **This union exists so the route can name a response at all.** The entry
417
417
  * carried `response: null` while the handler demonstrably answers something,
418
418
  * which reads in the generated reference as *this route returns nothing* —
419
- * the documented absence this project keeps paying for. A `null` there should
419
+ * a documented absence. A `null` there should
420
420
  * mean `204`, and on this route it did not.
421
421
  */
422
422
  export const invokeOrServiceResponse = z.union([invokeResponse, serviceCallResponse]);
@@ -546,7 +546,7 @@ export const jobResponse = z.object({
546
546
  * surface minted it cannot be routed correctly by anything.
547
547
  *
548
548
  * So the link shapes are fixed here rather than in whichever repo builds them.
549
- * **They moved to the auth portal** (auth-portal spec `2026-08-30`, D-A1): the
549
+ * **They moved to the auth portal**: the
550
550
  * console serves no credential page at all any more, and `{portal}` is the
551
551
  * cloud's `AUTH_PUBLIC_URL` — `auth.fleetless.dev` where the deployment has
552
552
  * that vhost, the cloud's own base where it does not, since the cloud renders
@@ -558,7 +558,7 @@ export const jobResponse = z.object({
558
558
  * | team invitation | `{portal}/accept-invite/{token}` |
559
559
  *
560
560
  * **An app user's links are not in this table, and cannot be** (2026-09-05,
561
- * D2/D5). Fleetless renders an app user no page, so there is no `{portal}` path
561
+ * Fleetless renders an app user no page, so there is no `{portal}` path
562
562
  * to name: the link points into the **developer's own app**, at the template
563
563
  * they configured (`appAuthConfig.invite_url`, `verify_url`, `reset_url`), with
564
564
  * the token substituted for `{token}`. That is why those fields are validated
@@ -569,7 +569,7 @@ export const jobResponse = z.object({
569
569
  * not a row to restore"*. It was designed; the answer was that the row belongs
570
570
  * to the developer and not to this table.
571
571
  *
572
- * The strings themselves live in `cloud/src/portal-paths.ts`, read by the
572
+ * The strings themselves live server-side, read by the
573
573
  * route that serves each page AND by the builder that mails it — one constant,
574
574
  * because the defect this table records happened again after it was written:
575
575
  * the mailer, the page and this table can each spell a path differently, and
package/dist/routes.js CHANGED
@@ -63,45 +63,43 @@ export const IN_HANDLER_ROUTES = [
63
63
  * **The three refusals every `auth: 'developer'` route inherits from its guard**,
64
64
  * spelled once rather than retyped eighty times.
65
65
  *
66
- * They are the arms of `cloud/src/auth.ts`'s `requireDeveloper`: no bearer or an
67
- * unverifiable one is `401 unauthorized`, an expired one `401 token_expired`, a
66
+ * They are the arms of the developer guard: no bearer or an unverifiable one is
67
+ * `401 unauthorized`, an expired one `401 token_expired`, a
68
68
  * vanished account or a bumped `token_version` `401 token_revoked`.
69
69
  *
70
70
  * **Three, not four: `403 forbidden` went with the Org Admins group.** It stood
71
71
  * for "an account that is no longer in the org's Org Admins group", and the
72
- * two-space cut leaves a Fleetless user who IS the team — `TokenRefusalReason`
73
- * in `cloud/src/auth.ts` is `'expired' | 'revoked' | 'invalid'`, and
74
- * `createRequireDeveloper` answers 401 codes only. A removed team member now
75
- * gets `401 token_revoked`; a reader of the API reference who branched on
72
+ * two identity spaces leave a Fleetless user who IS the team, and the guard's
73
+ * refusal reasons are `'expired' | 'revoked' | 'invalid'` — 401 codes only. A
74
+ * removed team member gets `401 token_revoked`; a reader of the API reference
75
+ * who branched on
76
76
  * `forbidden` to render "you lost console access" was branching on an answer no
77
77
  * developer-guarded route can send. The `forbidden` producers that remain
78
78
  * (`history.ts`, `commands.ts`, `cameras.ts`, `robots.ts`, `mcp.ts`) all sit on
79
79
  * `developer_or_client` or MCP surfaces, which is why `CLIENT_GUARD` keeps it.
80
80
  *
81
81
  * **Not `invalid_token`.** That code exists in `ERROR_CODES` and this guard has
82
- * never sent it; the three above are what `sendTokenRefusal` actually maps to.
83
- * Said plainly because the planning note for this file assumed otherwise, and a
84
- * documented refusal a caller cannot receive is the third failure mode in
85
- * CLAUDE.md's list.
82
+ * never sent it; the three above are what the token refusal actually maps to.
83
+ * Said plainly, because a documented refusal a caller cannot receive is worse
84
+ * than an undocumented one: a consumer branches on it and the branch is dead.
86
85
  */
87
86
  const DEVELOPER_GUARD = ['unauthorized', 'token_expired', 'token_revoked'];
88
87
  /**
89
- * The same, for `auth: 'developer_or_client'` — `createRequireDeveloperOrClient`,
90
- * which resolves a developer bearer, an end-user bearer **or** a server key
91
- * through one `resolveAnyToken`.
88
+ * The same, for `auth: 'developer_or_client'` — one guard resolving a developer
89
+ * bearer, an end-user bearer **or** a server key through a single token lookup.
92
90
  *
93
- * **The same four codes, not five.** `sendTokenRefusal` has a fifth arm,
94
- * `account_blocked`, and this list carried it for exactly one commit. Nothing
95
- * reaches it: `TokenRefusalReason` admits `'blocked'`, but no site in
96
- * `cloud/src` constructs one — the only reasons ever returned are `'invalid'`,
97
- * `'revoked'` and `'forbidden'`. `auth.ts` says why on the line where the check
98
- * used to be: D2 replaced "block the account" with "remove the assignment", so
99
- * an app user who loses access loses it because no assignment resolves, and
100
- * there is no blocked state left to re-check.
91
+ * **The same four codes, not five.** The token refusal has a fifth arm,
92
+ * `account_blocked`, which this list must not carry. Nothing reaches it: the
93
+ * refusal type admits `'blocked'`, but no site in
94
+ * the cloud constructs one — the only reasons ever returned are `'invalid'`,
95
+ * `'revoked'` and `'forbidden'`. Access is withdrawn by removing an assignment
96
+ * rather than by blocking an account, so an app user who loses access loses it
97
+ * because no assignment resolves, and there is no blocked state left to
98
+ * re-check.
101
99
  *
102
100
  * Listing it would have documented a refusal no caller can receive — the same
103
- * mistake as the `invalid_token` above, found by review rather than by any test
104
- * here, because a code in `ERROR_CODES` satisfies every check this file has.
101
+ * mistake as the `invalid_token` above. No check here catches it, because a
102
+ * code in `ERROR_CODES` satisfies every one of them.
105
103
  *
106
104
  * It keeps `forbidden`, which `DEVELOPER_GUARD` no longer carries: this guard's
107
105
  * routes have live 403 producers — a slug the role does not grant
@@ -420,7 +418,7 @@ export const ROUTES = [
420
418
  'listing beside it. `POST /api/client/register` answers `202` to the same fact, because there the caller is a stranger. `404 not_found` ' +
421
419
  'is the app, or a `role_id` that is not a role of it — a role of another app is refused rather than stored, since a user holding one ' +
422
420
  'would carry rights nothing in this app can resolve. **The password policy answers `400 validation_error`**, not a code of its own: the ' +
423
- 'twelve-character minimum is the `password` field\'s schema rule, and every route in this repository that takes a password refuses a ' +
421
+ 'twelve-character minimum is the `password` field\'s schema rule, and every route that takes a password refuses a ' +
424
422
  'short one exactly the way it refuses any other malformed field. An account created here is `active` immediately: a developer entering ' +
425
423
  'somebody by hand has made the decision the verification mail automates, and its address counts as proven. `409 target_state_conflict` ' +
426
424
  'names `default_role_id` when `role_id` is absent and the app has no default role, or its default names a role that no longer resolves ' +
@@ -590,7 +588,7 @@ export const ROUTES = [
590
588
  '`POST /api/client/invitations/accept`, the same answer one that expired or never existed gets — the developer withdrew it deliberately, ' +
591
589
  'and an answer saying so would tell whoever still holds the link that it was once real.',
592
590
  },
593
- /* --------------------------------------- the app's OIDC providers (D4) */
591
+ /* ------------------------------------------- the app's OIDC providers */
594
592
  {
595
593
  method: 'GET', path: '/api/apps/:id/oidc-providers', section: 'apps',
596
594
  summary: "Lists every OIDC provider configured on the app, enabled or not.",
@@ -975,7 +973,7 @@ export const ROUTES = [
975
973
  audience: 'internal', auth: 'none', rateLimited: true, ownerTier: false, status: 200,
976
974
  params: [], query: null, request: null, response: null,
977
975
  errors: ['rate_limited', 'validation_error', 'token_spent'], transport: 'http',
978
- notes: 'The identifier-first step, with nothing left to identify: Fleetless users are password-only (design D1/D7), so **this step does not ' +
976
+ notes: 'The identifier-first step, with nothing left to identify: Fleetless users are password-only, so **this step does not ' +
979
977
  'read the address at all** — it renders the password card for a known address, an unknown one and an empty one alike, and the login ' +
980
978
  'step below answers the same `401` for all three. That is a property of the shape rather than of two branches agreeing: there is no ' +
981
979
  'lookup here whose result could differ. A browser form post gets the password card; a JSON caller gets `{ "next" }`, which has no ' +
@@ -1133,10 +1131,10 @@ export const ROUTES = [
1133
1131
  'a token belongs to its own app\'s endpoint. The code is `forbidden` rather than `mcp_disabled` because nothing is switched off — the ' +
1134
1132
  'caller is at the wrong server — and per-app sign-in mints exactly such tokens, so the two states must not share a word. ' +
1135
1133
  '`mcp_access_denied` is gone with the per-user override and the ' +
1136
- 'group flag it read: every Fleetless user has MCP access here (D1). Stateless: a fresh transport per request, no session id, nothing ' +
1134
+ 'group flag it read: every Fleetless user has MCP access here. Stateless: a fresh transport per request, no session id, nothing ' +
1137
1135
  'survives the call.',
1138
1136
  },
1139
- /* ----------------------------------------- mcp (one app's own server, D7) */
1137
+ /* --------------------------------------------- mcp (one app's own server) */
1140
1138
  {
1141
1139
  method: 'POST', path: MCP_APP.endpoint, section: 'mcp',
1142
1140
  summary: "One app's MCP endpoint: the same stateless Streamable HTTP transport, carrying that app's robots.",
@@ -1174,11 +1172,11 @@ export const ROUTES = [
1174
1172
  notes: 'MCP\'s Streamable HTTP gives this path three verbs: `POST` carries JSON-RPC, `GET` opens the server-initiated SSE stream, and `DELETE` ' +
1175
1173
  'ends a session. This server has no sessions — the argument is in `MCP_PROTOCOL_VERSION`\'s own note, and a per-process session map is ' +
1176
1174
  'what breaks at the second cloud instance — so `GET` and `DELETE` answer `405`, which is what a client is built to fall back from. ' +
1177
- '\n\n**The `405` is this cloud\'s own answer, not the SDK\'s**, and the difference was measured: MCP SDK 1.30.0 opens an SSE stream on ' +
1175
+ '\n\n**The `405` is this cloud\'s own answer, not the SDK\'s**, and the two differ: MCP SDK 1.30.0 opens an SSE stream on ' +
1178
1176
  '`GET` (`handleGetRequest`) and answers `200` on `DELETE` (`handleDeleteRequest`), neither of which a stateless server has any ' +
1179
1177
  'business doing, so the cloud writes the `405` itself in the transport\'s own JSON-RPC error shape with `Allow: POST`. \n\n**The row exists so that the `405` is not a `404`.** An unregistered verb answers ' +
1180
1178
  '`404`, and at a path whose last segment is an app identifier a `404` already means *no such app* — one answer for two states, which is ' +
1181
- 'the failure this project keeps paying for. Registering the verb lets the endpoint say "this app\'s server is here; this verb is not ' +
1179
+ 'one answer for two states. Registering the verb lets the endpoint say "this app\'s server is here; this verb is not ' +
1182
1180
  'part of it". The central `/mcp` registers neither verb and does not need to: its path takes no parameter, so nothing can misread its ' +
1183
1181
  '`404`. \n\n**The `405` body is the transport\'s JSON-RPC error object, not the `apiError` envelope.** The three codes above are the ' +
1184
1182
  'refusals that come *first* — the app, its switch, then the bearer, in the order `POST` describes — and they are `apiError` because ' +
@@ -1228,7 +1226,7 @@ export const ROUTES = [
1228
1226
  'canonical public base, never from the friendly `mcp.fleetless.dev` alias or the request\'s `Host`**, because a client checks a minted ' +
1229
1227
  'token\'s `iss` and `aud` against these exact strings. \n\n**Unlike the central document, `authorization_endpoint` does not move to an ' +
1230
1228
  'auth-portal origin**, and there is nothing here for one to serve: this authorization step renders no Fleetless page at all. It ' +
1231
- 'redirects to the app\'s own `mcp_login_url` (D7), which is on the developer\'s origin already.',
1229
+ 'redirects to the app\'s own `mcp_login_url`, which is on the developer\'s origin already.',
1232
1230
  },
1233
1231
  {
1234
1232
  method: 'POST', path: MCP_APP.register, section: 'mcp',
@@ -1236,7 +1234,7 @@ export const ROUTES = [
1236
1234
  audience: 'client', auth: 'none', rateLimited: true, ownerTier: false, status: 201,
1237
1235
  params: [APP_IDENTIFIER], query: null, request: dynamicClientRegistrationRequest, response: dynamicClientRegistrationResponse,
1238
1236
  errors: ['rate_limited', 'not_found'], transport: 'http',
1239
- notes: 'RFC 7591, the same wire and the same handler as `POST /mcp/oauth/register` — `registerMcpDynamicClient`, one implementation, because a ' +
1237
+ notes: 'RFC 7591, the same wire and the same handler as `POST /mcp/oauth/register` — one implementation, because a ' +
1240
1238
  'second answer to "is this redirect URI acceptable" would agree with the first only by luck. The request schema is what the endpoint ' +
1241
1239
  'accepts rather than what it parses, for the reason that row gives: §3.2.2 needs two distinguishable refusals and one `safeParse` ' +
1242
1240
  'failure offers one. `client_name` and `redirect_uris` are read; `grant_types`, `response_types` and `scope` are accepted and ignored, ' +
@@ -1256,7 +1254,7 @@ export const ROUTES = [
1256
1254
  params: [APP_IDENTIFIER], query: oauthAuthorizeQuery, request: null, response: null,
1257
1255
  errors: ['not_found', 'target_state_conflict'], transport: 'http',
1258
1256
  notes: 'The same query as `GET /mcp/oauth/authorize`, read the same way — parameter by parameter, because the answers differ and one parse ' +
1259
- 'would collapse them. \n\n**Fleetless renders no page here, and that is the whole of D7.** The route writes an interaction — ten minutes, as the OIDC ones live ' +
1257
+ 'would collapse them. \n\n**Fleetless renders no page here**, and that is the whole of it. The route writes an interaction — ten minutes, as the OIDC ones live ' +
1260
1258
  '— and redirects to `appAuthConfig.mcp_login_url` with `{interaction}` filled in. The app then authenticates the person with its own ' +
1261
1259
  'UI, reads `GET /api/client/mcp/interactions/:id` to show the client\'s claimed name and the scopes it asked for, and calls approve or ' +
1262
1260
  'deny. \n\nClient and `redirect_uri` are validated first and a failure there never redirects — the open-redirect discipline `GET ' +
@@ -1273,7 +1271,7 @@ export const ROUTES = [
1273
1271
  'under `/api/client/mcp/interactions/:id`. `409 ' +
1274
1272
  'target_state_conflict` names `mcp_login_url` with rule `not_set`: MCP is enabled and no page is configured to send the person to. It ' +
1275
1273
  'is the same code and the same shape `send_mail` answers for an unconfigured `invite_url`, and the refusal is the honest one — ' +
1276
- 'Fleetless has nowhere to redirect, and rendering a page of its own instead would contradict D2.',
1274
+ 'Fleetless has nowhere to redirect, and rendering a page of its own would contradict the rule that Fleetless shows an app user no page.',
1277
1275
  },
1278
1276
  {
1279
1277
  method: 'POST', path: MCP_APP.token, section: 'mcp',
@@ -1516,7 +1514,7 @@ export const ROUTES = [
1516
1514
  '\n\n**It lists `rate_limited` and no other code, because every sign-in outcome it has is a redirect.** Success and failure alike are ' +
1517
1515
  'a `302` to the app\'s own ' +
1518
1516
  '`redirect_uri`: `?code=…&state=…` when a session was resolved, `?error=<clientOidcErrorCode>&state=…` when it was not, so the app ' +
1519
- 'renders its own message and can bind either answer to the request it started. Fleetless shows an app user no page (D2). \n\n**The one ' +
1517
+ 'renders its own message and can bind either answer to the request it started. Fleetless shows an app user no page. \n\n**The one ' +
1520
1518
  'exception is a `state` that resolves to no interaction** — unknown, hand-edited, or past its ten minutes. Then there is no confirmed ' +
1521
1519
  'redirect target to carry the answer to, and bouncing a browser to an unvalidated one is the hole the whole flow is arranged to avoid, ' +
1522
1520
  'so the cloud renders an HTML problem page at `400`. That is the only Fleetless-rendered surface an app user can reach. It is HTML ' +
@@ -1539,7 +1537,7 @@ export const ROUTES = [
1539
1537
  'is, not how vague the answer is: a mailed one-time code is a credential, an interaction id names a pending request, and the two ' +
1540
1538
  'deserve different advice on the app\'s own page.',
1541
1539
  },
1542
- /* --------------------------- the app's own MCP consent screen (D7) */
1540
+ /* ------------------------------- the app's own MCP consent screen */
1543
1541
  {
1544
1542
  method: 'GET', path: '/api/client/mcp/interactions/:id', section: 'client-auth',
1545
1543
  summary: 'Reads a pending MCP authorization so the app can draw its own consent screen.',
@@ -1574,7 +1572,7 @@ export const ROUTES = [
1574
1572
  query: null, request: null, response: clientMcpInteractionDecisionResponse,
1575
1573
  errors: [...CLIENT_GUARD, 'rate_limited', 'interaction_expired', 'mcp_disabled'], transport: 'http',
1576
1574
  notes: 'The person is already signed in **at the app**, by whatever means that app uses, and this is the app telling Fleetless what they ' +
1577
- 'decided. Fleetless never sees that sign-in, which is D7 in one sentence. \n\nThe guard admits all three caller kinds and the handler ' +
1575
+ 'decided. Fleetless never sees that sign-in. \n\nThe guard admits all three caller kinds and the handler ' +
1578
1576
  'takes one: a developer bearer or a server key reaching this is `401 unauthorized`, because a consent is a person\'s and a server key ' +
1579
1577
  'is not a person — the same shape `POST /api/client/password/change` has. `403 mcp_disabled` is the app\'s switch, re-read here as it is ' +
1580
1578
  'on every request — and it is the one refusal on this surface that names the switch, because reaching it needs an app-user session ' +
@@ -1615,7 +1613,7 @@ export const ROUTES = [
1615
1613
  params: [], query: null, request: null, response: mcpConsentGrantListResponse,
1616
1614
  errors: [...CLIENT_GUARD], transport: 'http',
1617
1615
  notes: '**So the developer\'s app can offer a "connected apps" screen of its own**, which is the only place an end user could ever be shown ' +
1618
- 'this: Fleetless renders no page for an app\'s users (D2), and the console is the developer\'s tool rather than their customers\'. ' +
1616
+ 'this: Fleetless renders no page for an app\'s users, and the console is the developer\'s tool rather than their customers\'. ' +
1619
1617
  '\n\nThe answer is about the bearer\'s own account and takes no user id — there is no id to pass and therefore nothing to pass the ' +
1620
1618
  'wrong one. The guard admits all three caller kinds because it is shared, and the handler takes one: a developer bearer or a server ' +
1621
1619
  'key is `401 unauthorized`, the shape `POST /api/client/password/change` has, because a consent is a person\'s and a server key is not ' +
@@ -1937,7 +1935,7 @@ export const ROUTES = [
1937
1935
  errors: [...CLIENT_GUARD, 'invalid_uuid', 'not_found', 'capability_required', 'validation_error'], transport: 'http',
1938
1936
  notes: 'Needs the `action_history` capability, and **this route is what makes that switch mean something** — it was unkeepable while nothing ' +
1939
1937
  'durable recorded what had run. Two residuals worth stating rather than implying away. `history` is a syntactically valid slug and ' +
1940
- 'Fastify matches a static segment first, so a robot with a service literally slugged `history` can no longer be **read** through ' +
1938
+ 'The router matches a static segment first, so a robot with a service literally slugged `history` can no longer be **read** through ' +
1941
1939
  '`GET /api/robots/:id/jobs/:slug`; invoking, cancelling and the listing are unaffected. And a run row names its actor by email address, ' +
1942
1940
  'so an end user holding this capability learns which other people have been driving the machine. `robot_id` in the query is shared with ' +
1943
1941
  'the org-wide read; a *different* one here is refused rather than quietly answered about the robot in the path.',
@@ -2278,7 +2276,7 @@ export const ROUTES = [
2278
2276
  'upload token minted by `POST /api/robots/:id/assets/sync`, verified in a `preParsing` hook so a refusal precedes the work rather than ' +
2279
2277
  'following it: a `preHandler` would already have buffered the whole file. The announced size is refused there too, before a single byte ' +
2280
2278
  'is read — it is an announcement and not a proof, so it only ever rejects early and never accepts early, and a body that lies small is ' +
2281
- 'still caught by the real length check. Past both, Fastify\'s own body limit answers a bare `413 bad_request` with neither ceiling nor ' +
2279
+ 'still caught by the real length check. Past both, the server\'s own body limit answers a bare `413 bad_request` with neither ceiling nor ' +
2282
2280
  'size in it. Rate limited per robot inside that same hook, which is why `rateLimited` is `false`: there is no rate-limiting preHandler ' +
2283
2281
  'registered on this route.',
2284
2282
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetless/contracts",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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",