@fleetless/contracts 6.1.0 → 6.2.0-next.2
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 +22 -0
- package/artifacts/constants.json +1 -0
- package/artifacts/routes.json +3 -1
- package/artifacts/schema/file-too-large-details.schema.json +30 -0
- package/dist/assets.d.ts +19 -3
- package/dist/assets.js +19 -3
- package/dist/client-auth.d.ts +15 -5
- package/dist/client-auth.js +15 -5
- package/dist/errors.d.ts +17 -1
- package/dist/errors.js +33 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/routes.js +9 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,28 @@ version.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`file_too_large`** (`413`): one asset file is larger than
|
|
15
|
+
`ASSET_FILE_MAX_BYTES` (one gigabyte, every plan, the URDF included).
|
|
16
|
+
`details` is `fileTooLargeDetails`: `max_bytes` and `size_bytes`, the
|
|
17
|
+
announced size or, when none (or a false one) was announced, the bytes that
|
|
18
|
+
arrived; `null` only when the body limit stopped the upload.
|
|
19
|
+
`ASSET_FILE_MAX_BYTES` is in `constants.json`. `plan_limit` and
|
|
20
|
+
`quota_exceeded` on an asset upload now mean only that the store is full.
|
|
21
|
+
- **`plan_limit` in `clientOidcErrorCode`.** A federated sign-in that would
|
|
22
|
+
create an app user beyond the org's plan `app_users` limit now redirects
|
|
23
|
+
with `?error=plan_limit`, so an app can tell that the plan is full.
|
|
24
|
+
`quota_exceeded` keeps one meaning there: the `max_end_users` protection
|
|
25
|
+
ceiling, which also covers orgs still on the beta. A consumer that switches
|
|
26
|
+
exhaustively over `ClientOidcErrorCode` gains one case.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **`POST /api/bridge/assets`** lists `file_too_large` and `plan_limit` among
|
|
31
|
+
its errors, and its notes describe the per-file limit instead of a bare
|
|
32
|
+
`413`.
|
|
33
|
+
|
|
12
34
|
## [6.1.0] — 2026-10-03
|
|
13
35
|
|
|
14
36
|
### Added
|
package/artifacts/constants.json
CHANGED
package/artifacts/routes.json
CHANGED
|
@@ -6488,11 +6488,13 @@
|
|
|
6488
6488
|
"rate_limited",
|
|
6489
6489
|
"validation_error",
|
|
6490
6490
|
"not_found",
|
|
6491
|
+
"file_too_large",
|
|
6492
|
+
"plan_limit",
|
|
6491
6493
|
"quota_exceeded",
|
|
6492
6494
|
"bad_request"
|
|
6493
6495
|
],
|
|
6494
6496
|
"transport": "http",
|
|
6495
|
-
"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. **
|
|
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."
|
|
6496
6498
|
},
|
|
6497
6499
|
{
|
|
6498
6500
|
"method": "GET",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"max_bytes": {
|
|
6
|
+
"type": "integer",
|
|
7
|
+
"exclusiveMinimum": 0,
|
|
8
|
+
"maximum": 9007199254740991,
|
|
9
|
+
"description": "The most one asset file can be, in bytes: `ASSET_FILE_MAX_BYTES`, the same on every plan."
|
|
10
|
+
},
|
|
11
|
+
"size_bytes": {
|
|
12
|
+
"anyOf": [
|
|
13
|
+
{
|
|
14
|
+
"type": "integer",
|
|
15
|
+
"exclusiveMinimum": 0,
|
|
16
|
+
"maximum": 9007199254740991
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "null"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
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
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": [
|
|
26
|
+
"max_bytes",
|
|
27
|
+
"size_bytes"
|
|
28
|
+
],
|
|
29
|
+
"additionalProperties": false
|
|
30
|
+
}
|
package/dist/assets.d.ts
CHANGED
|
@@ -196,6 +196,20 @@ export type AssetSyncResponse = z.infer<typeof assetSyncResponse>;
|
|
|
196
196
|
* the receiver enforces is two numbers that agree until one of them moves.
|
|
197
197
|
*/
|
|
198
198
|
export declare const ROBOT_ASSET_STORE_BYTES = 1000000000;
|
|
199
|
+
/**
|
|
200
|
+
* **How large one asset file may be, on every plan.**
|
|
201
|
+
*
|
|
202
|
+
* Its own number, not the store's, although both are a gigabyte today: the
|
|
203
|
+
* store says how much a robot may keep, this says how much the cloud will
|
|
204
|
+
* take in one request. A file over it is refused `413 file_too_large` with
|
|
205
|
+
* `fileTooLargeDetails`, before a byte is buffered when the size was
|
|
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
|
+
* as well, and retrying does not help.
|
|
209
|
+
*
|
|
210
|
+
* In `constants.json` for the same reason as `ROBOT_ASSET_STORE_BYTES`.
|
|
211
|
+
*/
|
|
212
|
+
export declare const ASSET_FILE_MAX_BYTES = 1000000000;
|
|
199
213
|
/**
|
|
200
214
|
* What a full store tells the caller — the same discipline as `job_queue_full`
|
|
201
215
|
* and `publisher_busy`: a refusal that names a state and no number leaves the
|
|
@@ -233,9 +247,11 @@ export type AssetStoreRefusedDetails = z.infer<typeof assetStoreRefusedDetails>;
|
|
|
233
247
|
* unexamined.
|
|
234
248
|
*
|
|
235
249
|
* There was a fourth, `too_large`, for a file over a per-file ceiling. That
|
|
236
|
-
* ceiling is gone
|
|
237
|
-
*
|
|
238
|
-
*
|
|
250
|
+
* ceiling is gone from this enum, not from the cloud: a file over
|
|
251
|
+
* `ASSET_FILE_MAX_BYTES` is refused by the cloud as `413 file_too_large`; a
|
|
252
|
+
* producer that relays it reports `refused` with no details — so the kind
|
|
253
|
+
* had no producer left here and one fewer thing to branch on is the whole of
|
|
254
|
+
* the gain.
|
|
239
255
|
*
|
|
240
256
|
* A consumer that cannot act on the distinction may still print `reference`
|
|
241
257
|
* alone and lose nothing it had before.
|
package/dist/assets.js
CHANGED
|
@@ -251,6 +251,20 @@ export const assetSyncResponse = z.object({
|
|
|
251
251
|
* the receiver enforces is two numbers that agree until one of them moves.
|
|
252
252
|
*/
|
|
253
253
|
export const ROBOT_ASSET_STORE_BYTES = 1_000_000_000;
|
|
254
|
+
/**
|
|
255
|
+
* **How large one asset file may be, on every plan.**
|
|
256
|
+
*
|
|
257
|
+
* Its own number, not the store's, although both are a gigabyte today: the
|
|
258
|
+
* store says how much a robot may keep, this says how much the cloud will
|
|
259
|
+
* take in one request. A file over it is refused `413 file_too_large` with
|
|
260
|
+
* `fileTooLargeDetails`, before a byte is buffered when the size was
|
|
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
|
+
* as well, and retrying does not help.
|
|
264
|
+
*
|
|
265
|
+
* In `constants.json` for the same reason as `ROBOT_ASSET_STORE_BYTES`.
|
|
266
|
+
*/
|
|
267
|
+
export const ASSET_FILE_MAX_BYTES = 1_000_000_000;
|
|
254
268
|
/**
|
|
255
269
|
* What a full store tells the caller — the same discipline as `job_queue_full`
|
|
256
270
|
* and `publisher_busy`: a refusal that names a state and no number leaves the
|
|
@@ -293,9 +307,11 @@ export const assetStoreRefusedDetails = z.object({
|
|
|
293
307
|
* unexamined.
|
|
294
308
|
*
|
|
295
309
|
* There was a fourth, `too_large`, for a file over a per-file ceiling. That
|
|
296
|
-
* ceiling is gone
|
|
297
|
-
*
|
|
298
|
-
*
|
|
310
|
+
* ceiling is gone from this enum, not from the cloud: a file over
|
|
311
|
+
* `ASSET_FILE_MAX_BYTES` is refused by the cloud as `413 file_too_large`; a
|
|
312
|
+
* producer that relays it reports `refused` with no details — so the kind
|
|
313
|
+
* had no producer left here and one fewer thing to branch on is the whole of
|
|
314
|
+
* the gain.
|
|
299
315
|
*
|
|
300
316
|
* A consumer that cannot act on the distinction may still print `reference`
|
|
301
317
|
* alone and lose nothing it had before.
|
package/dist/client-auth.d.ts
CHANGED
|
@@ -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
|
|
377
|
-
* quota allows, so no account can be
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
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
|
/**
|
package/dist/client-auth.js
CHANGED
|
@@ -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
|
|
439
|
-
* quota allows, so no account can be
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
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
|
@@ -209,10 +209,26 @@ export declare const orgLockedDetails: z.ZodObject<{
|
|
|
209
209
|
}>;
|
|
210
210
|
}, z.core.$strip>;
|
|
211
211
|
export type OrgLockedDetails = z.infer<typeof orgLockedDetails>;
|
|
212
|
+
/**
|
|
213
|
+
* The `details` of a `413 file_too_large` refusal (2026-10-03,
|
|
214
|
+
* fleetless/fleetless#136): one file is larger than `ASSET_FILE_MAX_BYTES`.
|
|
215
|
+
* Not a store and not a plan limit — those are `409 plan_limit` and
|
|
216
|
+
* `409 quota_exceeded`, and mean only "the store is full". `size_bytes` is
|
|
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.
|
|
222
|
+
*/
|
|
223
|
+
export declare const fileTooLargeDetails: z.ZodObject<{
|
|
224
|
+
max_bytes: z.ZodNumber;
|
|
225
|
+
size_bytes: z.ZodNullable<z.ZodNumber>;
|
|
226
|
+
}, z.core.$strip>;
|
|
227
|
+
export type FileTooLargeDetails = z.infer<typeof fileTooLargeDetails>;
|
|
212
228
|
/**
|
|
213
229
|
* The codes in use today. The wire deliberately allows any string — this
|
|
214
230
|
* list is the shared vocabulary, not a closed set, so a new refusal never
|
|
215
231
|
* needs a contracts release before it can be reported honestly.
|
|
216
232
|
*/
|
|
217
|
-
export declare const ERROR_CODES: readonly ["not_found", "validation_error", "bad_request", "unknown_datapoint", "invalid_token", "protocol_mismatch", "bridge_too_old", "invalid_frame", "duplicate_slug", "reserved_slug", "unknown_slug", "unknown_field_path", "unknown_type", "unknown_topic", "invalid_rate", "invalid_range", "config_conflict", "no_data", "robot_offline", "bridge_timeout", "unauthorized", "forbidden", "invalid_credentials", "token_expired", "token_revoked", "email_taken", "identifier_taken", "weak_password", "account_blocked", "busy", "parameter_invalid", "cancel_rejected", "job_lost", "job_unknown_to_bridge", "action_server_lost", "action_failed", "goal_rejected", "goal_send_failed", "result_failed", "goal_uncontrollable", "bridge_disconnected", "config_changed", "publisher_busy", "unknown_command", "not_subscribable", "camera_offline", "no_snapshot_yet", "live_unavailable", "wrong_kind", "not_recorded", "not_aggregatable", "quota_exceeded", "credential_in_use", "goal_timeout", "robot_in_use", "robot_deletion_partial", "job_queue_full", "invalid_uuid", "rate_limited", "tier_required", "token_spent", "service_timeout", "asset_missing", "dynamic_registration_disabled", "client_limit_reached", "idp_unavailable", "mcp_disabled", "tool_not_available", "capability_required", "last_owner", "role_name_taken", "role_in_use", "last_role", "target_state_conflict", "signup_closed", "draft_not_a_document", "internal_error", "not_cancellable", "unsupported_media_type", "wrong_browser", "invalid_yaml", "unstorable_yaml", "registration_closed", "domain_not_allowed", "email_unverified", "origin_not_allowed", "template_invalid", "provider_disabled", "provider_misconfigured", "invalid_redirect_uri", "interaction_expired", "invalid_code", "method_not_allowed", "plan_limit", "plan_required", "org_locked"];
|
|
233
|
+
export declare const ERROR_CODES: readonly ["not_found", "validation_error", "bad_request", "unknown_datapoint", "invalid_token", "protocol_mismatch", "bridge_too_old", "invalid_frame", "duplicate_slug", "reserved_slug", "unknown_slug", "unknown_field_path", "unknown_type", "unknown_topic", "invalid_rate", "invalid_range", "config_conflict", "no_data", "robot_offline", "bridge_timeout", "unauthorized", "forbidden", "invalid_credentials", "token_expired", "token_revoked", "email_taken", "identifier_taken", "weak_password", "account_blocked", "busy", "parameter_invalid", "cancel_rejected", "job_lost", "job_unknown_to_bridge", "action_server_lost", "action_failed", "goal_rejected", "goal_send_failed", "result_failed", "goal_uncontrollable", "bridge_disconnected", "config_changed", "publisher_busy", "unknown_command", "not_subscribable", "camera_offline", "no_snapshot_yet", "live_unavailable", "wrong_kind", "not_recorded", "not_aggregatable", "quota_exceeded", "credential_in_use", "goal_timeout", "robot_in_use", "robot_deletion_partial", "job_queue_full", "invalid_uuid", "rate_limited", "tier_required", "token_spent", "service_timeout", "asset_missing", "dynamic_registration_disabled", "client_limit_reached", "idp_unavailable", "mcp_disabled", "tool_not_available", "capability_required", "last_owner", "role_name_taken", "role_in_use", "last_role", "target_state_conflict", "signup_closed", "draft_not_a_document", "internal_error", "not_cancellable", "unsupported_media_type", "wrong_browser", "invalid_yaml", "unstorable_yaml", "registration_closed", "domain_not_allowed", "email_unverified", "origin_not_allowed", "template_invalid", "provider_disabled", "provider_misconfigured", "invalid_redirect_uri", "interaction_expired", "invalid_code", "method_not_allowed", "plan_limit", "plan_required", "org_locked", "file_too_large"];
|
|
218
234
|
export type ErrorCode = (typeof ERROR_CODES)[number];
|
package/dist/errors.js
CHANGED
|
@@ -135,6 +135,25 @@ export const orgLockedDetails = z.object({
|
|
|
135
135
|
description: '`payment`: a payment is missing. `migration`: the org did not choose what stays when the beta ended.',
|
|
136
136
|
}),
|
|
137
137
|
});
|
|
138
|
+
/**
|
|
139
|
+
* The `details` of a `413 file_too_large` refusal (2026-10-03,
|
|
140
|
+
* fleetless/fleetless#136): one file is larger than `ASSET_FILE_MAX_BYTES`.
|
|
141
|
+
* Not a store and not a plan limit — those are `409 plan_limit` and
|
|
142
|
+
* `409 quota_exceeded`, and mean only "the store is full". `size_bytes` is
|
|
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.
|
|
148
|
+
*/
|
|
149
|
+
export const fileTooLargeDetails = z.object({
|
|
150
|
+
max_bytes: z.number().int().positive().meta({
|
|
151
|
+
description: 'The most one asset file can be, in bytes: `ASSET_FILE_MAX_BYTES`, the same on every plan.',
|
|
152
|
+
}),
|
|
153
|
+
size_bytes: z.number().int().positive().nullable().meta({
|
|
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.',
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
138
157
|
/**
|
|
139
158
|
* The codes in use today. The wire deliberately allows any string — this
|
|
140
159
|
* list is the shared vocabulary, not a closed set, so a new refusal never
|
|
@@ -975,6 +994,10 @@ export const ERROR_CODES = [
|
|
|
975
994
|
* The existing `quota_exceeded` protection ceiling is still checked, and
|
|
976
995
|
* only after this one: it exists to stop runaway consumption, not to tell
|
|
977
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.
|
|
978
1001
|
*/
|
|
979
1002
|
'plan_limit',
|
|
980
1003
|
/**
|
|
@@ -995,4 +1018,14 @@ export const ERROR_CODES = [
|
|
|
995
1018
|
* payment, or the platform's own move off the beta.
|
|
996
1019
|
*/
|
|
997
1020
|
'org_locked',
|
|
1021
|
+
// 2026-10-03 — the per-file asset limit (#136).
|
|
1022
|
+
/**
|
|
1023
|
+
* `413`: one asset file is larger than `ASSET_FILE_MAX_BYTES`. Refused on
|
|
1024
|
+
* the announced size before the body is read, for every kind including the
|
|
1025
|
+
* URDF, otherwise on the bytes that arrived, or by the body limit when the
|
|
1026
|
+
* body ran past it. `details` is
|
|
1027
|
+
* `fileTooLargeDetails`. Not a store or plan limit, on any plan; retrying
|
|
1028
|
+
* does not help, only a smaller file does.
|
|
1029
|
+
*/
|
|
1030
|
+
'file_too_large',
|
|
998
1031
|
];
|
package/dist/index.d.ts
CHANGED
|
@@ -41,14 +41,14 @@ export { clientRobotListItem, clientRobotListResponse } from './client-robots.js
|
|
|
41
41
|
export type { ClientRobotListItem, ClientRobotListResponse } from './client-robots.js';
|
|
42
42
|
export { APP_USER_DISPLAY_NAME_MAX, APP_URL_PLACEHOLDERS, MAIL_TEMPLATE_VARIABLES, DEFAULT_MAIL_TEMPLATES, providerSlug, appUserStatus, appUser, appUserListResponse, createAppUserRequest, patchAppUserRequest, createAppInvitationRequest, appInvitation, pendingAppInvitation, appInvitationListResponse, appOidcProvider, appOidcProviderListResponse, createAppOidcProviderRequest, patchAppOidcProviderRequest, appUrlTemplate, allowedOrigin, emailDomain, appAuthConfig, appSignInMethods, appTwoFactorPolicy, appHomeUrl, hostedAccent, HOSTED_LOGO_MAX_BYTES, HOSTED_LOGO_TYPES, appHostedPages, putAppAuthRegistrationRequest, putAppAuthSignInRequest, putAppAuthUrlsRequest, putAppAuthMcpRequest, putAppAuthLookRequest, mailTemplateKind, appMailTemplate, appMailTemplateListResponse, putAppMailTemplateRequest, mailTemplatePreviewRequest, mailTemplatePreviewResponse, mailTemplateProblemDetails, mailOutcome, } from './app-users.js';
|
|
43
43
|
export type { AppUserStatus, AppUser, AppUserListResponse, CreateAppUserRequest, PatchAppUserRequest, CreateAppInvitationRequest, AppInvitation, PendingAppInvitation, AppInvitationListResponse, AppOidcProvider, AppOidcProviderListResponse, CreateAppOidcProviderRequest, PatchAppOidcProviderRequest, AppAuthConfig, AppSignInMethods, AppTwoFactorPolicy, AppHostedPages, PutAppAuthRegistrationRequest, PutAppAuthSignInRequest, PutAppAuthUrlsRequest, PutAppAuthMcpRequest, PutAppAuthLookRequest, MailTemplateKind, AppMailTemplate, AppMailTemplateListResponse, PutAppMailTemplateRequest, MailTemplatePreviewRequest, MailTemplatePreviewResponse, MailTemplateProblemDetails, MailOutcome, } from './app-users.js';
|
|
44
|
-
export { assetKind, URDF_ASSET_NAME, asset, urdfCompleteness, assetListResponse, assetsClearResponse, missingAssetQuery, assetSyncRequest, assetSyncResponse, assetSyncState, assetSyncStatus, assetFailure, assetFailureKind, assetStoreRefusedDetails, assetSyncBusyDetails, ROBOT_ASSET_STORE_BYTES, } from './assets.js';
|
|
44
|
+
export { assetKind, URDF_ASSET_NAME, asset, urdfCompleteness, assetListResponse, assetsClearResponse, missingAssetQuery, assetSyncRequest, assetSyncResponse, assetSyncState, assetSyncStatus, assetFailure, assetFailureKind, assetStoreRefusedDetails, assetSyncBusyDetails, ROBOT_ASSET_STORE_BYTES, ASSET_FILE_MAX_BYTES, } from './assets.js';
|
|
45
45
|
export type { AssetKind, Asset, UrdfCompleteness, AssetListResponse, AssetsClearResponse, MissingAssetQuery, AssetSyncRequest, AssetSyncResponse, AssetSyncState, AssetSyncStatus, AssetFailure, AssetFailureKind, AssetStoreRefusedDetails, AssetSyncBusyDetails, } from './assets.js';
|
|
46
46
|
export { auditActor, auditEvent, auditQuery, auditListResponse, AUDIT_CSV_COLUMNS, AUDIT_RETENTION_DAYS } from './audit.js';
|
|
47
47
|
export type { AuditActor, AuditEvent, AuditQuery, AuditListResponse } from './audit.js';
|
|
48
48
|
export { alertRowCondition, alertSeverity, alertState, datapointAlertRow, alertListResponse, orgFiringAlertsResponse, orgAlertsQuery, datapointDisplay, putDatapointDisplayRequest, } from './alerts.js';
|
|
49
49
|
export type { AlertRowCondition, AlertSeverity, AlertState, DatapointAlertRow, AlertListResponse, OrgFiringAlertsResponse, OrgAlertsQuery, DatapointDisplay, PutDatapointDisplayRequest, } from './alerts.js';
|
|
50
|
-
export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDetails, invalidCodeDetails, ERROR_CODES, planLimitDetails, assetPlanLimitDetails, planRequiredDetails, orgLockedDetails, } from './errors.js';
|
|
51
|
-
export type { ApiError, ParameterViolation, ParameterInvalidDetails, CancelRejectedDetails, InvalidCodeDetails, ErrorCode, PlanLimitDetails, AssetPlanLimitDetails, PlanRequiredDetails, OrgLockedDetails, } from './errors.js';
|
|
50
|
+
export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDetails, invalidCodeDetails, ERROR_CODES, planLimitDetails, assetPlanLimitDetails, planRequiredDetails, orgLockedDetails, fileTooLargeDetails, } from './errors.js';
|
|
51
|
+
export type { ApiError, ParameterViolation, ParameterInvalidDetails, CancelRejectedDetails, InvalidCodeDetails, ErrorCode, PlanLimitDetails, AssetPlanLimitDetails, PlanRequiredDetails, OrgLockedDetails, FileTooLargeDetails, } from './errors.js';
|
|
52
52
|
export { oauthErrorCode, oauthError, oauthRedirectResponse, oauthCodeTokenRequest, oauthRefreshTokenRequest, oauthTokenRequest, oauthTokenResponse, redirectUri, codeChallengeMethod, oauthAuthorizeQuery, dynamicClientRegistrationRequest, MCP_DCR_MAX_REDIRECT_URIS, dynamicClientRegistrationResponse, authorizationServerMetadata, protectedResourceMetadata, } from './oauth.js';
|
|
53
53
|
export type { OauthErrorCode, OauthError, OauthRedirectResponse, OauthCodeTokenRequest, OauthRefreshTokenRequest, OauthTokenRequest, OauthTokenResponse, RedirectUri, OauthAuthorizeQuery, DynamicClientRegistrationRequest, DynamicClientRegistrationResponse, AuthorizationServerMetadata, ProtectedResourceMetadata, } from './oauth.js';
|
|
54
54
|
export { ROUTES, ROUTE_SECTIONS, IN_HANDLER_ROUTES, developerSignInRoutes } from './routes.js';
|
package/dist/index.js
CHANGED
|
@@ -49,12 +49,14 @@ export { clientLoginRequest, clientRefreshRequest, clientLogoutRequest, clientRe
|
|
|
49
49
|
export { clientRobotListItem, clientRobotListResponse } from './client-robots.js';
|
|
50
50
|
// The per-app identity space.
|
|
51
51
|
export { APP_USER_DISPLAY_NAME_MAX, APP_URL_PLACEHOLDERS, MAIL_TEMPLATE_VARIABLES, DEFAULT_MAIL_TEMPLATES, providerSlug, appUserStatus, appUser, appUserListResponse, createAppUserRequest, patchAppUserRequest, createAppInvitationRequest, appInvitation, pendingAppInvitation, appInvitationListResponse, appOidcProvider, appOidcProviderListResponse, createAppOidcProviderRequest, patchAppOidcProviderRequest, appUrlTemplate, allowedOrigin, emailDomain, appAuthConfig, appSignInMethods, appTwoFactorPolicy, appHomeUrl, hostedAccent, HOSTED_LOGO_MAX_BYTES, HOSTED_LOGO_TYPES, appHostedPages, putAppAuthRegistrationRequest, putAppAuthSignInRequest, putAppAuthUrlsRequest, putAppAuthMcpRequest, putAppAuthLookRequest, mailTemplateKind, appMailTemplate, appMailTemplateListResponse, putAppMailTemplateRequest, mailTemplatePreviewRequest, mailTemplatePreviewResponse, mailTemplateProblemDetails, mailOutcome, } from './app-users.js';
|
|
52
|
-
export { assetKind, URDF_ASSET_NAME, asset, urdfCompleteness, assetListResponse, assetsClearResponse, missingAssetQuery, assetSyncRequest, assetSyncResponse, assetSyncState, assetSyncStatus, assetFailure, assetFailureKind, assetStoreRefusedDetails, assetSyncBusyDetails, ROBOT_ASSET_STORE_BYTES, } from './assets.js';
|
|
52
|
+
export { assetKind, URDF_ASSET_NAME, asset, urdfCompleteness, assetListResponse, assetsClearResponse, missingAssetQuery, assetSyncRequest, assetSyncResponse, assetSyncState, assetSyncStatus, assetFailure, assetFailureKind, assetStoreRefusedDetails, assetSyncBusyDetails, ROBOT_ASSET_STORE_BYTES, ASSET_FILE_MAX_BYTES, } from './assets.js';
|
|
53
53
|
export { auditActor, auditEvent, auditQuery, auditListResponse, AUDIT_CSV_COLUMNS, AUDIT_RETENTION_DAYS } from './audit.js';
|
|
54
54
|
export { alertRowCondition, alertSeverity, alertState, datapointAlertRow, alertListResponse, orgFiringAlertsResponse, orgAlertsQuery, datapointDisplay, putDatapointDisplayRequest, } from './alerts.js';
|
|
55
55
|
export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDetails, invalidCodeDetails, ERROR_CODES,
|
|
56
56
|
// 2026-10-02 — plans (#103). The plan errors (I-3).
|
|
57
|
-
planLimitDetails, assetPlanLimitDetails, planRequiredDetails, orgLockedDetails,
|
|
57
|
+
planLimitDetails, assetPlanLimitDetails, planRequiredDetails, orgLockedDetails,
|
|
58
|
+
// 2026-10-03 — the per-file asset limit (#136).
|
|
59
|
+
fileTooLargeDetails, } from './errors.js';
|
|
58
60
|
export { oauthErrorCode, oauthError, oauthRedirectResponse, oauthCodeTokenRequest, oauthRefreshTokenRequest, oauthTokenRequest, oauthTokenResponse, redirectUri, codeChallengeMethod, oauthAuthorizeQuery, dynamicClientRegistrationRequest, MCP_DCR_MAX_REDIRECT_URIS, dynamicClientRegistrationResponse, authorizationServerMetadata, protectedResourceMetadata, } from './oauth.js';
|
|
59
61
|
export { ROUTES, ROUTE_SECTIONS, IN_HANDLER_ROUTES, developerSignInRoutes } from './routes.js';
|
|
60
62
|
// 2026-10-02 — plans (#103). The catalogue (I-1) only: `PLANS` and `ADDONS`
|
package/dist/realtime.d.ts
CHANGED
|
@@ -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
|
@@ -2852,16 +2852,18 @@ export const ROUTES = [
|
|
|
2852
2852
|
summary: 'Takes one asset file from a robot during a sync.',
|
|
2853
2853
|
audience: 'internal', auth: 'robot_upload', rateLimited: false, ownerTier: false, status: 201,
|
|
2854
2854
|
params: [], query: null, request: null, response: asset,
|
|
2855
|
-
errors: ['unauthorized', 'rate_limited', 'validation_error', 'not_found', 'quota_exceeded', 'bad_request'], transport: 'http',
|
|
2855
|
+
errors: ['unauthorized', 'rate_limited', 'validation_error', 'not_found', 'file_too_large', 'plan_limit', 'quota_exceeded', 'bad_request'], transport: 'http',
|
|
2856
2856
|
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 ' +
|
|
2857
2857
|
'and its announced size — rides in the `x-fleetless-asset-*` headers `ASSET_UPLOAD_HEADERS` names. The credential is a short-lived ' +
|
|
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
|
-
'following it: a `preHandler` would already have buffered the whole file. **
|
|
2860
|
-
'
|
|
2861
|
-
'
|
|
2862
|
-
'
|
|
2863
|
-
'
|
|
2864
|
-
'
|
|
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
|
+
'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; 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. ' +
|
|
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 ' +
|
|
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 ' +
|
|
2865
|
+
'robot inside that same hook, which is why `rateLimited` is `false`: there is no rate-limiting preHandler registered on this route. The ' +
|
|
2866
|
+
'URDF itself is never refused for the store; only meshes and textures are charged against it.',
|
|
2865
2867
|
},
|
|
2866
2868
|
/* ------------------------------------ realtime and bridge transports */
|
|
2867
2869
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetless/contracts",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0-next.2",
|
|
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",
|