@fleetless/contracts 6.2.0-next.1 → 6.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 CHANGED
@@ -9,14 +9,23 @@ version.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [6.2.0] — 2026-10-04
13
+
12
14
  ### Added
13
15
 
14
16
  - **`file_too_large`** (`413`): one asset file is larger than
15
17
  `ASSET_FILE_MAX_BYTES` (one gigabyte, every plan, the URDF included).
16
- `details` is `fileTooLargeDetails`: `max_bytes` and `size_bytes`, which is
17
- `null` when no size was announced and the body limit stopped the upload.
18
+ `details` is `fileTooLargeDetails`: `max_bytes` and `size_bytes`, the
19
+ announced size or, when none (or a false one) was announced, the bytes that
20
+ arrived; `null` only when the body limit stopped the upload.
18
21
  `ASSET_FILE_MAX_BYTES` is in `constants.json`. `plan_limit` and
19
22
  `quota_exceeded` on an asset upload now mean only that the store is full.
23
+ - **`plan_limit` in `clientOidcErrorCode`.** A federated sign-in that would
24
+ create an app user beyond the org's plan `app_users` limit now redirects
25
+ with `?error=plan_limit`, so an app can tell that the plan is full.
26
+ `quota_exceeded` keeps one meaning there: the `max_end_users` protection
27
+ ceiling, which also covers orgs still on the beta. A consumer that switches
28
+ exhaustively over `ClientOidcErrorCode` gains one case.
20
29
 
21
30
  ### Changed
22
31
 
@@ -6494,7 +6494,7 @@
6494
6494
  "bad_request"
6495
6495
  ],
6496
6496
  "transport": "http",
6497
- "notes": "The body is the **raw file bytes**, not JSON, so it has no request schema; everything about the file — its kind, its name, its sync id and its announced size — rides in the `x-fleetless-asset-*` headers `ASSET_UPLOAD_HEADERS` names. The credential is a short-lived upload token minted by `POST /api/robots/:id/assets/sync`, verified in a `preParsing` hook so a refusal precedes the work rather than following it: a `preHandler` would already have buffered the whole file. **One file can be at most `ASSET_FILE_MAX_BYTES`**, on every plan and for every kind, the URDF included: an announced size over it answers `413 file_too_large` with `max_bytes` and `size_bytes` before a byte is buffered, and a body over the server's body limit without a truthful size answers the same code with `size_bytes: null`. Retrying does not help. A file that fits but finds the store full answers `409 plan_limit` (or `409 quota_exceeded` for an organisation still on the beta) carrying `store_bytes`, `used_bytes` and `size_bytes`, while the sync carries on with the next file. Rate limited per robot inside that same hook, which is why `rateLimited` is `false`: there is no rate-limiting preHandler registered on this route. The URDF itself is never refused for the store; only meshes and textures are charged against it."
6497
+ "notes": "The body is the **raw file bytes**, not JSON, so it has no request schema; everything about the file — its kind, its name, its sync id and its announced size — rides in the `x-fleetless-asset-*` headers `ASSET_UPLOAD_HEADERS` names. The credential is a short-lived upload token minted by `POST /api/robots/:id/assets/sync`, verified in a `preParsing` hook so a refusal precedes the work rather than following it: a `preHandler` would already have buffered the whole file. **One file can be at most `ASSET_FILE_MAX_BYTES`**, on every plan and for every kind, the URDF included: an announced size over it answers `413 file_too_large` with `max_bytes` and `size_bytes` before a byte is buffered; without a truthful size, a body over the limit answers the same code with the bytes that arrived as `size_bytes`, or with `size_bytes: null` when it ran past the server's body limit and nobody counted the bytes. Retrying does not help. A file that fits but finds the store full answers `409 plan_limit` (or `409 quota_exceeded` for an organisation still on the beta) carrying `store_bytes`, `used_bytes` and `size_bytes`, while the sync carries on with the next file. Rate limited per robot inside that same hook, which is why `rateLimited` is `false`: there is no rate-limiting preHandler registered on this route. The URDF itself is never refused for the store; only meshes and textures are charged against it."
6498
6498
  },
6499
6499
  {
6500
6500
  "method": "GET",
@@ -19,7 +19,7 @@
19
19
  "type": "null"
20
20
  }
21
21
  ],
22
- "description": "The refused file's announced size, in bytes; `null` when no size was announced and the body limit stopped the upload."
22
+ "description": "The refused file's size, in bytes: the announced size, or the bytes that arrived when none (or a false one) was announced; `null` only when the body limit stopped the upload and nobody counted the bytes."
23
23
  }
24
24
  },
25
25
  "required": [
package/dist/assets.d.ts CHANGED
@@ -203,8 +203,8 @@ export declare const ROBOT_ASSET_STORE_BYTES = 1000000000;
203
203
  * store says how much a robot may keep, this says how much the cloud will
204
204
  * take in one request. A file over it is refused `413 file_too_large` with
205
205
  * `fileTooLargeDetails`, before a byte is buffered when the size was
206
- * announced (`ASSET_UPLOAD_HEADERS.size`) and by the server's body limit when
207
- * it was not. That refusal is not a store or plan limit, applies to the URDF
206
+ * announced (`ASSET_UPLOAD_HEADERS.size`), and otherwise on the bytes that
207
+ * arrived, or by the server's body limit when the body ran past it. That refusal is not a store or plan limit, applies to the URDF
208
208
  * as well, and retrying does not help.
209
209
  *
210
210
  * In `constants.json` for the same reason as `ROBOT_ASSET_STORE_BYTES`.
package/dist/assets.js CHANGED
@@ -258,8 +258,8 @@ export const ROBOT_ASSET_STORE_BYTES = 1_000_000_000;
258
258
  * store says how much a robot may keep, this says how much the cloud will
259
259
  * take in one request. A file over it is refused `413 file_too_large` with
260
260
  * `fileTooLargeDetails`, before a byte is buffered when the size was
261
- * announced (`ASSET_UPLOAD_HEADERS.size`) and by the server's body limit when
262
- * it was not. That refusal is not a store or plan limit, applies to the URDF
261
+ * announced (`ASSET_UPLOAD_HEADERS.size`), and otherwise on the bytes that
262
+ * arrived, or by the server's body limit when the body ran past it. That refusal is not a store or plan limit, applies to the URDF
263
263
  * as well, and retrying does not help.
264
264
  *
265
265
  * In `constants.json` for the same reason as `ROBOT_ASSET_STORE_BYTES`.
@@ -373,14 +373,23 @@ export type ClientOidcExchangeRequest = z.infer<typeof clientOidcExchangeRequest
373
373
  * `provider_misconfigured`, `provider_disabled` — the provider's or the
374
374
  * developer's to fix, and the app can say so.
375
375
  * - `invalid_request` — the start parameters did not hold up.
376
- * - `quota_exceeded` — the org has as many app users as its `max_end_users`
377
- * quota allows, so no account can be created for this identity. Named rather
378
- * than folded into `no_access`, for `domain_not_allowed`'s reason: it is not
379
- * about the person, the app can say what happened, and the remedy belongs to
380
- * the developer rather than to whoever is trying to sign in. It is raised
376
+ * - `quota_exceeded` — the org is at the protection ceiling: it has as many
377
+ * app users as its `max_end_users` quota allows, so no account can be
378
+ * created for this identity. This also covers an org still on the beta,
379
+ * whose plan is not enforced yet. Named rather than folded into
380
+ * `no_access`, for `domain_not_allowed`'s reason: it is not about the
381
+ * person, the app can say what happened, and the remedy belongs to the
382
+ * developer rather than to whoever is trying to sign in. It is raised
381
383
  * **only where an account would be created** — an identity that already has
382
384
  * one signs in at the quota exactly as it does under it, because refusing a
383
385
  * sign-in would turn a protection limit into an outage.
386
+ * - `plan_limit` — the org's plan has no room for another app user: it is at
387
+ * its plan's `app_users` limit (fleetless/fleetless#103), counted across
388
+ * every app of the org with pending invitations included. Checked before
389
+ * the protection ceiling, and raised, like `quota_exceeded`, **only where
390
+ * an account would be created**. The remedy is a higher plan or an add-on,
391
+ * which is why it is not `quota_exceeded`. It is the same code the other
392
+ * app-user doors answer as `409 plan_limit`.
384
393
  */
385
394
  export declare const clientOidcErrorCode: z.ZodEnum<{
386
395
  no_access: "no_access";
@@ -395,6 +404,7 @@ export declare const clientOidcErrorCode: z.ZodEnum<{
395
404
  provider_disabled: "provider_disabled";
396
405
  invalid_request: "invalid_request";
397
406
  quota_exceeded: "quota_exceeded";
407
+ plan_limit: "plan_limit";
398
408
  }>;
399
409
  export type ClientOidcErrorCode = z.infer<typeof clientOidcErrorCode>;
400
410
  /**
@@ -435,14 +435,23 @@ export const clientOidcExchangeRequest = z
435
435
  * `provider_misconfigured`, `provider_disabled` — the provider's or the
436
436
  * developer's to fix, and the app can say so.
437
437
  * - `invalid_request` — the start parameters did not hold up.
438
- * - `quota_exceeded` — the org has as many app users as its `max_end_users`
439
- * quota allows, so no account can be created for this identity. Named rather
440
- * than folded into `no_access`, for `domain_not_allowed`'s reason: it is not
441
- * about the person, the app can say what happened, and the remedy belongs to
442
- * the developer rather than to whoever is trying to sign in. It is raised
438
+ * - `quota_exceeded` — the org is at the protection ceiling: it has as many
439
+ * app users as its `max_end_users` quota allows, so no account can be
440
+ * created for this identity. This also covers an org still on the beta,
441
+ * whose plan is not enforced yet. Named rather than folded into
442
+ * `no_access`, for `domain_not_allowed`'s reason: it is not about the
443
+ * person, the app can say what happened, and the remedy belongs to the
444
+ * developer rather than to whoever is trying to sign in. It is raised
443
445
  * **only where an account would be created** — an identity that already has
444
446
  * one signs in at the quota exactly as it does under it, because refusing a
445
447
  * sign-in would turn a protection limit into an outage.
448
+ * - `plan_limit` — the org's plan has no room for another app user: it is at
449
+ * its plan's `app_users` limit (fleetless/fleetless#103), counted across
450
+ * every app of the org with pending invitations included. Checked before
451
+ * the protection ceiling, and raised, like `quota_exceeded`, **only where
452
+ * an account would be created**. The remedy is a higher plan or an add-on,
453
+ * which is why it is not `quota_exceeded`. It is the same code the other
454
+ * app-user doors answer as `409 plan_limit`.
446
455
  */
447
456
  export const clientOidcErrorCode = z.enum([
448
457
  'no_access',
@@ -457,6 +466,7 @@ export const clientOidcErrorCode = z.enum([
457
466
  'provider_disabled',
458
467
  'invalid_request',
459
468
  'quota_exceeded',
469
+ 'plan_limit',
460
470
  ]);
461
471
  /* ------------------------------------------------ MCP, delegated login -- */
462
472
  /**
package/dist/errors.d.ts CHANGED
@@ -214,9 +214,11 @@ export type OrgLockedDetails = z.infer<typeof orgLockedDetails>;
214
214
  * fleetless/fleetless#136): one file is larger than `ASSET_FILE_MAX_BYTES`.
215
215
  * Not a store and not a plan limit — those are `409 plan_limit` and
216
216
  * `409 quota_exceeded`, and mean only "the store is full". `size_bytes` is
217
- * the announced size; it is `null` only when the sender announced none (or a
218
- * false one) and the server's body limit stopped the upload, because then
219
- * nobody counted the bytes.
217
+ * the file's size: the announced one when the refusal came before the body
218
+ * was read, or the bytes that arrived when no size (or a false one) was
219
+ * announced and the body still fit the server's body limit. It is `null` only
220
+ * when the body limit stopped the upload, because then nobody counted the
221
+ * bytes.
220
222
  */
221
223
  export declare const fileTooLargeDetails: z.ZodObject<{
222
224
  max_bytes: z.ZodNumber;
package/dist/errors.js CHANGED
@@ -140,16 +140,18 @@ export const orgLockedDetails = z.object({
140
140
  * fleetless/fleetless#136): one file is larger than `ASSET_FILE_MAX_BYTES`.
141
141
  * Not a store and not a plan limit — those are `409 plan_limit` and
142
142
  * `409 quota_exceeded`, and mean only "the store is full". `size_bytes` is
143
- * the announced size; it is `null` only when the sender announced none (or a
144
- * false one) and the server's body limit stopped the upload, because then
145
- * nobody counted the bytes.
143
+ * the file's size: the announced one when the refusal came before the body
144
+ * was read, or the bytes that arrived when no size (or a false one) was
145
+ * announced and the body still fit the server's body limit. It is `null` only
146
+ * when the body limit stopped the upload, because then nobody counted the
147
+ * bytes.
146
148
  */
147
149
  export const fileTooLargeDetails = z.object({
148
150
  max_bytes: z.number().int().positive().meta({
149
151
  description: 'The most one asset file can be, in bytes: `ASSET_FILE_MAX_BYTES`, the same on every plan.',
150
152
  }),
151
153
  size_bytes: z.number().int().positive().nullable().meta({
152
- description: 'The refused file\'s announced size, in bytes; `null` when no size was announced and the body limit stopped the upload.',
154
+ description: 'The refused file\'s size, in bytes: the announced size, or the bytes that arrived when none (or a false one) was announced; `null` only when the body limit stopped the upload and nobody counted the bytes.',
153
155
  }),
154
156
  });
155
157
  /**
@@ -992,6 +994,10 @@ export const ERROR_CODES = [
992
994
  * The existing `quota_exceeded` protection ceiling is still checked, and
993
995
  * only after this one: it exists to stop runaway consumption, not to tell
994
996
  * a developer what their plan allows.
997
+ *
998
+ * At an OIDC callback the same refusal reaches the app as the
999
+ * `clientOidcErrorCode` of the same name, redirected rather than answered
1000
+ * as JSON.
995
1001
  */
996
1002
  'plan_limit',
997
1003
  /**
@@ -1016,7 +1022,8 @@ export const ERROR_CODES = [
1016
1022
  /**
1017
1023
  * `413`: one asset file is larger than `ASSET_FILE_MAX_BYTES`. Refused on
1018
1024
  * the announced size before the body is read, for every kind including the
1019
- * URDF, or by the body limit when no size was announced. `details` is
1025
+ * URDF, otherwise on the bytes that arrived, or by the body limit when the
1026
+ * body ran past it. `details` is
1020
1027
  * `fileTooLargeDetails`. Not a store or plan limit, on any plan; retrying
1021
1028
  * does not help, only a smaller file does.
1022
1029
  */
@@ -260,10 +260,10 @@ export type DatapointEvent = z.infer<typeof datapointEvent>;
260
260
  */
261
261
  export declare const liveSessionEndReason: z.ZodEnum<{
262
262
  unknown: "unknown";
263
+ plan_limit: "plan_limit";
263
264
  publish_failed: "publish_failed";
264
265
  robot_offline: "robot_offline";
265
266
  config_changed: "config_changed";
266
- plan_limit: "plan_limit";
267
267
  released_by_peer: "released_by_peer";
268
268
  revoked: "revoked";
269
269
  expired: "expired";
@@ -292,10 +292,10 @@ export declare const liveSessionEvent: z.ZodObject<{
292
292
  state: z.ZodLiteral<"ended">;
293
293
  reason: z.ZodEnum<{
294
294
  unknown: "unknown";
295
+ plan_limit: "plan_limit";
295
296
  publish_failed: "publish_failed";
296
297
  robot_offline: "robot_offline";
297
298
  config_changed: "config_changed";
298
- plan_limit: "plan_limit";
299
299
  released_by_peer: "released_by_peer";
300
300
  revoked: "revoked";
301
301
  expired: "expired";
package/dist/routes.js CHANGED
@@ -2858,7 +2858,8 @@ export const ROUTES = [
2858
2858
  'upload token minted by `POST /api/robots/:id/assets/sync`, verified in a `preParsing` hook so a refusal precedes the work rather than ' +
2859
2859
  'following it: a `preHandler` would already have buffered the whole file. **One file can be at most `ASSET_FILE_MAX_BYTES`**, on every ' +
2860
2860
  'plan and for every kind, the URDF included: an announced size over it answers `413 file_too_large` with `max_bytes` and `size_bytes` ' +
2861
- 'before a byte is buffered, and a body over the server\'s body limit without a truthful size answers the same code with `size_bytes: null`. ' +
2861
+ 'before a byte is buffered; without a truthful size, a body over the limit answers the same code with the bytes that arrived as `size_bytes`, ' +
2862
+ 'or with `size_bytes: null` when it ran past the server\'s body limit and nobody counted the bytes. ' +
2862
2863
  'Retrying does not help. A file that fits but finds the store full answers `409 plan_limit` (or `409 quota_exceeded` for an organisation ' +
2863
2864
  'still on the beta) carrying `store_bytes`, `used_bytes` and `size_bytes`, while the sync carries on with the next file. Rate limited per ' +
2864
2865
  'robot inside that same hook, which is why `rateLimited` is `false`: there is no rate-limiting preHandler registered on this route. The ' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetless/contracts",
3
- "version": "6.2.0-next.1",
3
+ "version": "6.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",