@fleetless/contracts 6.0.0-next.3 → 6.1.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/artifacts/openapi.json +636 -1
  3. package/artifacts/routes.json +101 -0
  4. package/artifacts/schema/addon-catalogue-entry.schema.json +75 -0
  5. package/artifacts/schema/addon-key.schema.json +11 -0
  6. package/artifacts/schema/admin-plan-change-request.schema.json +171 -0
  7. package/artifacts/schema/asset-plan-limit-details.schema.json +108 -0
  8. package/artifacts/schema/audit-actor.schema.json +2 -1
  9. package/artifacts/schema/audit-event.schema.json +2 -1
  10. package/artifacts/schema/audit-list-response.schema.json +2 -1
  11. package/artifacts/schema/org-addons.schema.json +39 -0
  12. package/artifacts/schema/org-lock.schema.json +23 -0
  13. package/artifacts/schema/org-locked-details.schema.json +17 -0
  14. package/artifacts/schema/org-plan-usage.schema.json +45 -0
  15. package/artifacts/schema/org-plan.schema.json +450 -0
  16. package/artifacts/schema/pending-plan-change.schema.json +112 -0
  17. package/artifacts/schema/plan-catalogue-entry.schema.json +226 -0
  18. package/artifacts/schema/plan-change-keep.schema.json +45 -0
  19. package/artifacts/schema/plan-change-reason.schema.json +10 -0
  20. package/artifacts/schema/plan-change-request.schema.json +71 -0
  21. package/artifacts/schema/plan-currency.schema.json +8 -0
  22. package/artifacts/schema/plan-features.schema.json +37 -0
  23. package/artifacts/schema/plan-id.schema.json +10 -0
  24. package/artifacts/schema/plan-limit-details.schema.json +87 -0
  25. package/artifacts/schema/plan-limits.schema.json +113 -0
  26. package/artifacts/schema/plan-overrides.schema.json +103 -0
  27. package/artifacts/schema/plan-prices.schema.json +33 -0
  28. package/artifacts/schema/plan-required-details.schema.json +42 -0
  29. package/dist/audit.d.ts +10 -0
  30. package/dist/audit.js +8 -1
  31. package/dist/errors.d.ts +138 -1
  32. package/dist/errors.js +94 -0
  33. package/dist/index.d.ts +6 -2
  34. package/dist/index.js +11 -1
  35. package/dist/plans.d.ts +525 -0
  36. package/dist/plans.js +442 -0
  37. package/dist/realtime.d.ts +2 -0
  38. package/dist/realtime.js +6 -0
  39. package/dist/routes.d.ts +5 -1
  40. package/dist/routes.js +58 -0
  41. package/package.json +1 -1
@@ -0,0 +1,103 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "seats": {
6
+ "anyOf": [
7
+ {
8
+ "type": "integer",
9
+ "exclusiveMinimum": 0,
10
+ "maximum": 9007199254740991
11
+ },
12
+ {
13
+ "type": "null"
14
+ }
15
+ ]
16
+ },
17
+ "robots": {
18
+ "anyOf": [
19
+ {
20
+ "type": "integer",
21
+ "exclusiveMinimum": 0,
22
+ "maximum": 9007199254740991
23
+ },
24
+ {
25
+ "type": "null"
26
+ }
27
+ ]
28
+ },
29
+ "apps": {
30
+ "anyOf": [
31
+ {
32
+ "type": "integer",
33
+ "exclusiveMinimum": 0,
34
+ "maximum": 9007199254740991
35
+ },
36
+ {
37
+ "type": "null"
38
+ }
39
+ ]
40
+ },
41
+ "app_users": {
42
+ "anyOf": [
43
+ {
44
+ "type": "integer",
45
+ "exclusiveMinimum": 0,
46
+ "maximum": 9007199254740991
47
+ },
48
+ {
49
+ "type": "null"
50
+ }
51
+ ]
52
+ },
53
+ "live_video_ms_per_month": {
54
+ "anyOf": [
55
+ {
56
+ "type": "integer",
57
+ "exclusiveMinimum": 0,
58
+ "maximum": 9007199254740991
59
+ },
60
+ {
61
+ "type": "null"
62
+ }
63
+ ]
64
+ },
65
+ "asset_bytes_per_robot": {
66
+ "anyOf": [
67
+ {
68
+ "type": "integer",
69
+ "exclusiveMinimum": 0,
70
+ "maximum": 9007199254740991
71
+ },
72
+ {
73
+ "type": "null"
74
+ }
75
+ ]
76
+ },
77
+ "history_days": {
78
+ "anyOf": [
79
+ {
80
+ "type": "integer",
81
+ "exclusiveMinimum": 0,
82
+ "maximum": 9007199254740991
83
+ },
84
+ {
85
+ "type": "null"
86
+ }
87
+ ]
88
+ },
89
+ "audit_days": {
90
+ "anyOf": [
91
+ {
92
+ "type": "integer",
93
+ "exclusiveMinimum": 0,
94
+ "maximum": 9007199254740991
95
+ },
96
+ {
97
+ "type": "null"
98
+ }
99
+ ]
100
+ }
101
+ },
102
+ "additionalProperties": false
103
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "eur_month": {
6
+ "type": "integer",
7
+ "minimum": 0,
8
+ "maximum": 9007199254740991
9
+ },
10
+ "usd_month": {
11
+ "type": "integer",
12
+ "minimum": 0,
13
+ "maximum": 9007199254740991
14
+ },
15
+ "eur_year": {
16
+ "type": "integer",
17
+ "minimum": 0,
18
+ "maximum": 9007199254740991
19
+ },
20
+ "usd_year": {
21
+ "type": "integer",
22
+ "minimum": 0,
23
+ "maximum": 9007199254740991
24
+ }
25
+ },
26
+ "required": [
27
+ "eur_month",
28
+ "usd_month",
29
+ "eur_year",
30
+ "usd_year"
31
+ ],
32
+ "additionalProperties": false
33
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "feature": {
6
+ "type": "string",
7
+ "enum": [
8
+ "app_mcp",
9
+ "two_factor",
10
+ "require_two_factor",
11
+ "app_oidc",
12
+ "hosted_logo",
13
+ "audit_export",
14
+ "addons"
15
+ ]
16
+ },
17
+ "plan": {
18
+ "type": "string",
19
+ "enum": [
20
+ "basic",
21
+ "plus",
22
+ "pro",
23
+ "enterprise"
24
+ ]
25
+ },
26
+ "required_plan": {
27
+ "type": "string",
28
+ "enum": [
29
+ "basic",
30
+ "plus",
31
+ "pro",
32
+ "enterprise"
33
+ ]
34
+ }
35
+ },
36
+ "required": [
37
+ "feature",
38
+ "plan",
39
+ "required_plan"
40
+ ],
41
+ "additionalProperties": false
42
+ }
package/dist/audit.d.ts CHANGED
@@ -27,6 +27,13 @@ import { z } from 'zod';
27
27
  *
28
28
  * `developer` is a Fleetless user. It kept its name through both redesigns
29
29
  * because it was always right: the person who configures robots.
30
+ *
31
+ * `fleetless` (2026-10-02, fleetless/fleetless#103) records what the
32
+ * platform itself did, with no member behind it: the admin plan route, a
33
+ * plan change landing at its `effective_at`, a lock taking effect, and the
34
+ * beta-to-plan switch. Its `id` is the nil uuid and its `label` is always
35
+ * `Fleetless`, because there is no row to look up — the actor and the label
36
+ * are the same constant for every one of these events.
30
37
  */
31
38
  export declare const auditActor: z.ZodObject<{
32
39
  kind: z.ZodEnum<{
@@ -35,6 +42,7 @@ export declare const auditActor: z.ZodObject<{
35
42
  bridge: "bridge";
36
43
  end_user: "end_user";
37
44
  app_user: "app_user";
45
+ fleetless: "fleetless";
38
46
  }>;
39
47
  id: z.ZodUUID;
40
48
  label: z.ZodString;
@@ -52,6 +60,7 @@ export declare const auditEvent: z.ZodObject<{
52
60
  bridge: "bridge";
53
61
  end_user: "end_user";
54
62
  app_user: "app_user";
63
+ fleetless: "fleetless";
55
64
  }>;
56
65
  id: z.ZodUUID;
57
66
  label: z.ZodString;
@@ -90,6 +99,7 @@ export declare const auditListResponse: z.ZodObject<{
90
99
  bridge: "bridge";
91
100
  end_user: "end_user";
92
101
  app_user: "app_user";
102
+ fleetless: "fleetless";
93
103
  }>;
94
104
  id: z.ZodUUID;
95
105
  label: z.ZodString;
package/dist/audit.js CHANGED
@@ -28,9 +28,16 @@ import { wireSeqCursor, wireTimestampMs } from './common.js';
28
28
  *
29
29
  * `developer` is a Fleetless user. It kept its name through both redesigns
30
30
  * because it was always right: the person who configures robots.
31
+ *
32
+ * `fleetless` (2026-10-02, fleetless/fleetless#103) records what the
33
+ * platform itself did, with no member behind it: the admin plan route, a
34
+ * plan change landing at its `effective_at`, a lock taking effect, and the
35
+ * beta-to-plan switch. Its `id` is the nil uuid and its `label` is always
36
+ * `Fleetless`, because there is no row to look up — the actor and the label
37
+ * are the same constant for every one of these events.
31
38
  */
32
39
  export const auditActor = z.object({
33
- kind: z.enum(['developer', 'end_user', 'app_user', 'server_key', 'bridge']),
40
+ kind: z.enum(['developer', 'end_user', 'app_user', 'server_key', 'bridge', 'fleetless']),
34
41
  id: z.uuid(),
35
42
  label: z.string().min(1).max(200),
36
43
  });
package/dist/errors.d.ts CHANGED
@@ -72,10 +72,147 @@ export declare const invalidCodeDetails: z.ZodObject<{
72
72
  attempts_left: z.ZodNumber;
73
73
  }, z.core.$strip>;
74
74
  export type InvalidCodeDetails = z.infer<typeof invalidCodeDetails>;
75
+ /**
76
+ * The `details` of a `409 plan_limit` refusal (2026-10-02, fleetless/fleetless#103).
77
+ * `history_days` and `audit_days` are excluded from `limit` — they size a
78
+ * retention window, not a quota an action can exceed, so nothing ever
79
+ * refuses against them. `max` is the effective ceiling the refusal was
80
+ * checked against: the plan's own number, or — for `asset_bytes_per_robot`
81
+ * — the org's whole pool, `robots × asset_bytes_per_robot`. `plan` is the
82
+ * plan that refused, which during a pending downgrade is the **target**
83
+ * plan, not the one still in effect. `lifted_by` is `nextPlanRaising` for
84
+ * `plan` and `limit`, paired with the add-on that raises the same limit when
85
+ * the org's plan has `planFeature.addons` — never both at once, since a plan
86
+ * that cannot buy add-ons has nothing in that field.
87
+ */
88
+ export declare const planLimitDetails: z.ZodObject<{
89
+ limit: z.ZodEnum<{
90
+ apps: "apps";
91
+ robots: "robots";
92
+ seats: "seats";
93
+ app_users: "app_users";
94
+ live_video_ms_per_month: "live_video_ms_per_month";
95
+ asset_bytes_per_robot: "asset_bytes_per_robot";
96
+ }>;
97
+ used: z.ZodNumber;
98
+ max: z.ZodNumber;
99
+ plan: z.ZodEnum<{
100
+ basic: "basic";
101
+ plus: "plus";
102
+ pro: "pro";
103
+ enterprise: "enterprise";
104
+ }>;
105
+ lifted_by: z.ZodObject<{
106
+ plan: z.ZodNullable<z.ZodEnum<{
107
+ basic: "basic";
108
+ plus: "plus";
109
+ pro: "pro";
110
+ enterprise: "enterprise";
111
+ }>>;
112
+ addon: z.ZodNullable<z.ZodEnum<{
113
+ apps: "apps";
114
+ robots: "robots";
115
+ seats: "seats";
116
+ app_user_packs: "app_user_packs";
117
+ live_video_packs: "live_video_packs";
118
+ }>>;
119
+ }, z.core.$strip>;
120
+ }, z.core.$strip>;
121
+ export type PlanLimitDetails = z.infer<typeof planLimitDetails>;
122
+ /**
123
+ * `planLimitDetails` plus the three numbers `assetStoreRefusedDetails`
124
+ * already carries, for the one refusal that answers both at once: a robot
125
+ * asset sync refused because the **org's** pool (not a per-robot ceiling,
126
+ * which no longer exists) has no room left. `store_bytes` and `used_bytes`
127
+ * are the org's totals, the same pool `max` and `used` above describe; they
128
+ * ride twice because the bridge reads these three specific keys off any
129
+ * `409` and a consumer parsing only `assetStoreRefusedDetails` must keep
130
+ * working unchanged.
131
+ *
132
+ * **The two carried-over fields are re-described, not re-typed.** Plain
133
+ * `.extend(assetStoreRefusedDetails.shape)` would publish that schema's own
134
+ * per-robot wording (`'The robot\'s store, in bytes.'`) on a refusal that is
135
+ * never about one robot — so `store_bytes` and `used_bytes` get their own
136
+ * `.meta()` here, org-wide, while keeping the exact same underlying type
137
+ * (`.meta()` clones a schema and only replaces its registered metadata, so
138
+ * the bridge's own parser sees the same shape it always did). `size_bytes`
139
+ * is unchanged: the refused upload's size means the same thing in both
140
+ * contexts.
141
+ */
142
+ export declare const assetPlanLimitDetails: z.ZodObject<{
143
+ limit: z.ZodEnum<{
144
+ apps: "apps";
145
+ robots: "robots";
146
+ seats: "seats";
147
+ app_users: "app_users";
148
+ live_video_ms_per_month: "live_video_ms_per_month";
149
+ asset_bytes_per_robot: "asset_bytes_per_robot";
150
+ }>;
151
+ used: z.ZodNumber;
152
+ max: z.ZodNumber;
153
+ plan: z.ZodEnum<{
154
+ basic: "basic";
155
+ plus: "plus";
156
+ pro: "pro";
157
+ enterprise: "enterprise";
158
+ }>;
159
+ lifted_by: z.ZodObject<{
160
+ plan: z.ZodNullable<z.ZodEnum<{
161
+ basic: "basic";
162
+ plus: "plus";
163
+ pro: "pro";
164
+ enterprise: "enterprise";
165
+ }>>;
166
+ addon: z.ZodNullable<z.ZodEnum<{
167
+ apps: "apps";
168
+ robots: "robots";
169
+ seats: "seats";
170
+ app_user_packs: "app_user_packs";
171
+ live_video_packs: "live_video_packs";
172
+ }>>;
173
+ }, z.core.$strip>;
174
+ store_bytes: z.ZodNumber;
175
+ used_bytes: z.ZodNumber;
176
+ size_bytes: z.ZodNumber;
177
+ }, z.core.$strip>;
178
+ export type AssetPlanLimitDetails = z.infer<typeof assetPlanLimitDetails>;
179
+ /** The `details` of a `403 plan_required` refusal: the feature, the org's own plan, and the cheapest plan that has it (`requiredPlanFor`). */
180
+ export declare const planRequiredDetails: z.ZodObject<{
181
+ feature: z.ZodEnum<{
182
+ require_two_factor: "require_two_factor";
183
+ two_factor: "two_factor";
184
+ app_mcp: "app_mcp";
185
+ app_oidc: "app_oidc";
186
+ hosted_logo: "hosted_logo";
187
+ audit_export: "audit_export";
188
+ addons: "addons";
189
+ }>;
190
+ plan: z.ZodEnum<{
191
+ basic: "basic";
192
+ plus: "plus";
193
+ pro: "pro";
194
+ enterprise: "enterprise";
195
+ }>;
196
+ required_plan: z.ZodEnum<{
197
+ basic: "basic";
198
+ plus: "plus";
199
+ pro: "pro";
200
+ enterprise: "enterprise";
201
+ }>;
202
+ }, z.core.$strip>;
203
+ export type PlanRequiredDetails = z.infer<typeof planRequiredDetails>;
204
+ /** The `details` of a `403 org_locked` refusal: why the org is locked, matching `orgLock.reason`. */
205
+ export declare const orgLockedDetails: z.ZodObject<{
206
+ reason: z.ZodEnum<{
207
+ migration: "migration";
208
+ payment: "payment";
209
+ }>;
210
+ }, z.core.$strip>;
211
+ export type OrgLockedDetails = z.infer<typeof orgLockedDetails>;
75
212
  /**
76
213
  * The codes in use today. The wire deliberately allows any string — this
77
214
  * list is the shared vocabulary, not a closed set, so a new refusal never
78
215
  * needs a contracts release before it can be reported honestly.
79
216
  */
80
- 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"];
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"];
81
218
  export type ErrorCode = (typeof ERROR_CODES)[number];
package/dist/errors.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  import { z } from 'zod';
3
+ import { assetStoreRefusedDetails } from './assets.js';
4
+ import { addonKey, planFeature, planId, planLimitKey } from './plans.js';
3
5
  import { bridgeCancelResultEntry } from './protocol.js';
4
6
  /**
5
7
  * The one error shape of the REST and realtime APIs: a stable
@@ -63,6 +65,68 @@ export const invalidCodeDetails = z.object({
63
65
  description: 'How many more wrong codes this code or challenge takes before it is spent. `0` means the next attempt answers `410 token_spent`.',
64
66
  }),
65
67
  });
68
+ /**
69
+ * The `details` of a `409 plan_limit` refusal (2026-10-02, fleetless/fleetless#103).
70
+ * `history_days` and `audit_days` are excluded from `limit` — they size a
71
+ * retention window, not a quota an action can exceed, so nothing ever
72
+ * refuses against them. `max` is the effective ceiling the refusal was
73
+ * checked against: the plan's own number, or — for `asset_bytes_per_robot`
74
+ * — the org's whole pool, `robots × asset_bytes_per_robot`. `plan` is the
75
+ * plan that refused, which during a pending downgrade is the **target**
76
+ * plan, not the one still in effect. `lifted_by` is `nextPlanRaising` for
77
+ * `plan` and `limit`, paired with the add-on that raises the same limit when
78
+ * the org's plan has `planFeature.addons` — never both at once, since a plan
79
+ * that cannot buy add-ons has nothing in that field.
80
+ */
81
+ export const planLimitDetails = z.object({
82
+ limit: planLimitKey.exclude(['history_days', 'audit_days']),
83
+ used: z.number().int().nonnegative(),
84
+ max: z.number().int().nonnegative(),
85
+ plan: planId,
86
+ lifted_by: z.object({
87
+ plan: planId.nullable(),
88
+ addon: addonKey.nullable(),
89
+ }),
90
+ });
91
+ /**
92
+ * `planLimitDetails` plus the three numbers `assetStoreRefusedDetails`
93
+ * already carries, for the one refusal that answers both at once: a robot
94
+ * asset sync refused because the **org's** pool (not a per-robot ceiling,
95
+ * which no longer exists) has no room left. `store_bytes` and `used_bytes`
96
+ * are the org's totals, the same pool `max` and `used` above describe; they
97
+ * ride twice because the bridge reads these three specific keys off any
98
+ * `409` and a consumer parsing only `assetStoreRefusedDetails` must keep
99
+ * working unchanged.
100
+ *
101
+ * **The two carried-over fields are re-described, not re-typed.** Plain
102
+ * `.extend(assetStoreRefusedDetails.shape)` would publish that schema's own
103
+ * per-robot wording (`'The robot\'s store, in bytes.'`) on a refusal that is
104
+ * never about one robot — so `store_bytes` and `used_bytes` get their own
105
+ * `.meta()` here, org-wide, while keeping the exact same underlying type
106
+ * (`.meta()` clones a schema and only replaces its registered metadata, so
107
+ * the bridge's own parser sees the same shape it always did). `size_bytes`
108
+ * is unchanged: the refused upload's size means the same thing in both
109
+ * contexts.
110
+ */
111
+ export const assetPlanLimitDetails = planLimitDetails.extend({
112
+ ...assetStoreRefusedDetails.shape,
113
+ store_bytes: assetStoreRefusedDetails.shape.store_bytes.meta({
114
+ description: 'The org\'s asset pool, in bytes: `robots × asset_bytes_per_robot`.',
115
+ }),
116
+ used_bytes: assetStoreRefusedDetails.shape.used_bytes.meta({
117
+ description: 'Bytes the org\'s assets occupy, across every robot, before this upload.',
118
+ }),
119
+ });
120
+ /** The `details` of a `403 plan_required` refusal: the feature, the org's own plan, and the cheapest plan that has it (`requiredPlanFor`). */
121
+ export const planRequiredDetails = z.object({
122
+ feature: planFeature,
123
+ plan: planId,
124
+ required_plan: planId,
125
+ });
126
+ /** The `details` of a `403 org_locked` refusal: why the org is locked, matching `orgLock.reason`. */
127
+ export const orgLockedDetails = z.object({
128
+ reason: z.enum(['payment', 'migration']),
129
+ });
66
130
  /**
67
131
  * The codes in use today. The wire deliberately allows any string — this
68
132
  * list is the shared vocabulary, not a closed set, so a new refusal never
@@ -891,4 +955,34 @@ export const ERROR_CODES = [
891
955
  * is no enumeration oracle.
892
956
  */
893
957
  'method_not_allowed',
958
+ // 2026-10-02 — plans (#103).
959
+ /**
960
+ * `409`: the action would push the org past one of its plan's limits —
961
+ * a seat, a robot, an app, an app user, this month's live video, or a
962
+ * robot's asset storage. Answered **before anything is written**, so a
963
+ * refused create or invite never leaves a half-made row behind. `details`
964
+ * is `planLimitDetails` (or `assetPlanLimitDetails` for an asset-storage
965
+ * refusal): which limit, how much is used, the ceiling, the plan that
966
+ * refused, and what would lift it — a higher plan, an add-on, or neither.
967
+ * The existing `quota_exceeded` protection ceiling is still checked, and
968
+ * only after this one: it exists to stop runaway consumption, not to tell
969
+ * a developer what their plan allows.
970
+ */
971
+ 'plan_limit',
972
+ /**
973
+ * `403`: the feature the caller reached for is not on the org's plan — a
974
+ * two-factor requirement, an app's OIDC federation, a hosted logo, the
975
+ * audit export, or add-ons themselves. `details` is `planRequiredDetails`:
976
+ * the feature, the org's own plan, and the cheapest plan that has it, so
977
+ * the console can offer the upgrade in the same breath as the refusal.
978
+ */
979
+ 'plan_required',
980
+ /**
981
+ * `403`: the org is locked and may only move to Basic. `details` is
982
+ * `orgLockedDetails`, naming why — a failed payment, or the platform's own
983
+ * move off the beta. Distinct from `plan_required`, which is about a
984
+ * feature the plan never had; this is about an org that is not allowed to
985
+ * spend on any plan right now.
986
+ */
987
+ 'org_locked',
894
988
  ];
package/dist/index.d.ts CHANGED
@@ -47,9 +47,13 @@ export { auditActor, auditEvent, auditQuery, auditListResponse, AUDIT_CSV_COLUMN
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 } from './errors.js';
51
- export type { ApiError, ParameterViolation, ParameterInvalidDetails, CancelRejectedDetails, InvalidCodeDetails, ErrorCode } from './errors.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';
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';
55
55
  export type { RouteEntry, RouteParam, RouteAudience, RouteAuth, RouteSection, RouteMethod, RouteTransport, } from './routes.js';
56
+ export { planId, PLAN_ORDER, planLimitKey, planLimits, planFeature, planFeatures, planPrices, planSupport, planCatalogueEntry, addonKey, addonCatalogueEntry, usdCentsFromEurCents, yearlyEurCents, pricesFromEurMonth, PLANS, ADDONS, requiredPlanFor, nextPlanRaising, } from './plans.js';
57
+ export type { PlanId, PlanLimitKey, PlanLimits, PlanFeature, PlanFeatures, PlanPrices, PlanSupport, PlanCatalogueEntry, AddonKey, AddonCatalogueEntry, } from './plans.js';
58
+ export { planCurrency, orgAddons, orgPlanUsage, planChangeKeep, planChangeReason, pendingPlanChange, orgLock, orgPlan, planChangeRequest, planOverrides, adminPlanChangeRequest, } from './plans.js';
59
+ export type { PlanCurrency, OrgAddons, OrgPlanUsage, PlanChangeKeep, PlanChangeReason, PendingPlanChange, OrgLock, OrgPlan, PlanChangeRequest, PlanOverrides, AdminPlanChangeRequest, } from './plans.js';
package/dist/index.js CHANGED
@@ -52,6 +52,16 @@ export { APP_USER_DISPLAY_NAME_MAX, APP_URL_PLACEHOLDERS, MAIL_TEMPLATE_VARIABLE
52
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';
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
- export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDetails, invalidCodeDetails, ERROR_CODES } from './errors.js';
55
+ export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDetails, invalidCodeDetails, ERROR_CODES,
56
+ // 2026-10-02 — plans (#103). The plan errors (I-3).
57
+ planLimitDetails, assetPlanLimitDetails, planRequiredDetails, orgLockedDetails, } from './errors.js';
56
58
  export { oauthErrorCode, oauthError, oauthRedirectResponse, oauthCodeTokenRequest, oauthRefreshTokenRequest, oauthTokenRequest, oauthTokenResponse, redirectUri, codeChallengeMethod, oauthAuthorizeQuery, dynamicClientRegistrationRequest, MCP_DCR_MAX_REDIRECT_URIS, dynamicClientRegistrationResponse, authorizationServerMetadata, protectedResourceMetadata, } from './oauth.js';
57
59
  export { ROUTES, ROUTE_SECTIONS, IN_HANDLER_ROUTES, developerSignInRoutes } from './routes.js';
60
+ // 2026-10-02 — plans (#103). The catalogue (I-1) only: `PLANS` and `ADDONS`
61
+ // are deliberately not constants exports (`constants.json` is vendored by
62
+ // the bridge and must stay unchanged) but are exported values of this
63
+ // barrel like any other schema module.
64
+ export { planId, PLAN_ORDER, planLimitKey, planLimits, planFeature, planFeatures, planPrices, planSupport, planCatalogueEntry, addonKey, addonCatalogueEntry, usdCentsFromEurCents, yearlyEurCents, pricesFromEurMonth, PLANS, ADDONS, requiredPlanFor, nextPlanRaising, } from './plans.js';
65
+ // 2026-10-02 — plans (#103). The organization's plan, plan changes and the
66
+ // admin request (I-2).
67
+ export { planCurrency, orgAddons, orgPlanUsage, planChangeKeep, planChangeReason, pendingPlanChange, orgLock, orgPlan, planChangeRequest, planOverrides, adminPlanChangeRequest, } from './plans.js';