@fleetless/contracts 6.0.0 → 6.1.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/artifacts/openapi.json +692 -1
  3. package/artifacts/routes.json +101 -0
  4. package/artifacts/schema/addon-catalogue-entry.schema.json +81 -0
  5. package/artifacts/schema/addon-key.schema.json +11 -0
  6. package/artifacts/schema/admin-plan-change-request.schema.json +189 -0
  7. package/artifacts/schema/asset-plan-limit-details.schema.json +115 -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 +44 -0
  12. package/artifacts/schema/org-lock.schema.json +25 -0
  13. package/artifacts/schema/org-locked-details.schema.json +18 -0
  14. package/artifacts/schema/org-plan-usage.schema.json +51 -0
  15. package/artifacts/schema/org-plan.schema.json +500 -0
  16. package/artifacts/schema/pending-plan-change.schema.json +123 -0
  17. package/artifacts/schema/plan-catalogue-entry.schema.json +249 -0
  18. package/artifacts/schema/plan-change-keep.schema.json +49 -0
  19. package/artifacts/schema/plan-change-reason.schema.json +10 -0
  20. package/artifacts/schema/plan-change-request.schema.json +77 -0
  21. package/artifacts/schema/plan-currency.schema.json +8 -0
  22. package/artifacts/schema/plan-features.schema.json +44 -0
  23. package/artifacts/schema/plan-id.schema.json +10 -0
  24. package/artifacts/schema/plan-limit-details.schema.json +94 -0
  25. package/artifacts/schema/plan-limits.schema.json +121 -0
  26. package/artifacts/schema/plan-overrides.schema.json +111 -0
  27. package/artifacts/schema/plan-prices.schema.json +37 -0
  28. package/artifacts/schema/plan-required-details.schema.json +45 -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 +104 -0
  33. package/dist/index.d.ts +6 -2
  34. package/dist/index.js +11 -1
  35. package/dist/plans.d.ts +507 -0
  36. package/dist/plans.js +487 -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 +59 -0
  41. package/package.json +1 -1
@@ -0,0 +1,111 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "seats": {
6
+ "description": "Replaces the plan's `seats`. `null` clears the override.",
7
+ "anyOf": [
8
+ {
9
+ "type": "integer",
10
+ "exclusiveMinimum": 0,
11
+ "maximum": 9007199254740991
12
+ },
13
+ {
14
+ "type": "null"
15
+ }
16
+ ]
17
+ },
18
+ "robots": {
19
+ "description": "Replaces the plan's `robots`. `null` clears the override.",
20
+ "anyOf": [
21
+ {
22
+ "type": "integer",
23
+ "exclusiveMinimum": 0,
24
+ "maximum": 9007199254740991
25
+ },
26
+ {
27
+ "type": "null"
28
+ }
29
+ ]
30
+ },
31
+ "apps": {
32
+ "description": "Replaces the plan's `apps`. `null` clears the override.",
33
+ "anyOf": [
34
+ {
35
+ "type": "integer",
36
+ "exclusiveMinimum": 0,
37
+ "maximum": 9007199254740991
38
+ },
39
+ {
40
+ "type": "null"
41
+ }
42
+ ]
43
+ },
44
+ "app_users": {
45
+ "description": "Replaces the plan's `app_users`. `null` clears the override.",
46
+ "anyOf": [
47
+ {
48
+ "type": "integer",
49
+ "exclusiveMinimum": 0,
50
+ "maximum": 9007199254740991
51
+ },
52
+ {
53
+ "type": "null"
54
+ }
55
+ ]
56
+ },
57
+ "live_video_ms_per_month": {
58
+ "description": "Replaces the plan's `live_video_ms_per_month`. `null` clears the override.",
59
+ "anyOf": [
60
+ {
61
+ "type": "integer",
62
+ "exclusiveMinimum": 0,
63
+ "maximum": 9007199254740991
64
+ },
65
+ {
66
+ "type": "null"
67
+ }
68
+ ]
69
+ },
70
+ "asset_bytes_per_robot": {
71
+ "description": "Replaces the plan's `asset_bytes_per_robot`. `null` clears the override.",
72
+ "anyOf": [
73
+ {
74
+ "type": "integer",
75
+ "exclusiveMinimum": 0,
76
+ "maximum": 9007199254740991
77
+ },
78
+ {
79
+ "type": "null"
80
+ }
81
+ ]
82
+ },
83
+ "history_days": {
84
+ "description": "Replaces the plan's `history_days`. `null` clears the override.",
85
+ "anyOf": [
86
+ {
87
+ "type": "integer",
88
+ "exclusiveMinimum": 0,
89
+ "maximum": 9007199254740991
90
+ },
91
+ {
92
+ "type": "null"
93
+ }
94
+ ]
95
+ },
96
+ "audit_days": {
97
+ "description": "Replaces the plan's `audit_days`. `null` clears the override.",
98
+ "anyOf": [
99
+ {
100
+ "type": "integer",
101
+ "exclusiveMinimum": 0,
102
+ "maximum": 9007199254740991
103
+ },
104
+ {
105
+ "type": "null"
106
+ }
107
+ ]
108
+ }
109
+ },
110
+ "additionalProperties": false
111
+ }
@@ -0,0 +1,37 @@
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
+ "description": "Per month in euro cents, excluding VAT."
10
+ },
11
+ "usd_month": {
12
+ "type": "integer",
13
+ "minimum": 0,
14
+ "maximum": 9007199254740991,
15
+ "description": "Per month in US dollar cents, excluding VAT: the euro price × 1.15, rounded up to a whole dollar."
16
+ },
17
+ "eur_year": {
18
+ "type": "integer",
19
+ "minimum": 0,
20
+ "maximum": 9007199254740991,
21
+ "description": "Per year in euro cents, excluding VAT: twelve months less 15 %."
22
+ },
23
+ "usd_year": {
24
+ "type": "integer",
25
+ "minimum": 0,
26
+ "maximum": 9007199254740991,
27
+ "description": "Per year in US dollar cents, excluding VAT: the yearly euro price × 1.15, rounded up to a whole dollar."
28
+ }
29
+ },
30
+ "required": [
31
+ "eur_month",
32
+ "usd_month",
33
+ "eur_year",
34
+ "usd_year"
35
+ ],
36
+ "additionalProperties": false
37
+ }
@@ -0,0 +1,45 @@
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
+ "description": "The feature the action needs."
17
+ },
18
+ "plan": {
19
+ "type": "string",
20
+ "enum": [
21
+ "basic",
22
+ "plus",
23
+ "pro",
24
+ "enterprise"
25
+ ],
26
+ "description": "The org's current plan."
27
+ },
28
+ "required_plan": {
29
+ "type": "string",
30
+ "enum": [
31
+ "basic",
32
+ "plus",
33
+ "pro",
34
+ "enterprise"
35
+ ],
36
+ "description": "The cheapest plan that has the feature."
37
+ }
38
+ },
39
+ "required": [
40
+ "feature",
41
+ "plan",
42
+ "required_plan"
43
+ ],
44
+ "additionalProperties": false
45
+ }
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,76 @@ 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']).meta({ description: 'The limit the action would exceed.' }),
83
+ used: z.number().int().nonnegative().meta({ description: 'How much of the limit the org uses now.' }),
84
+ max: z.number().int().nonnegative().meta({
85
+ description: 'The limit. For `asset_bytes_per_robot`, the org\'s whole pool: robots × the plan\'s bytes per robot.',
86
+ }),
87
+ plan: planId.meta({ description: 'The plan whose limit refused: the target plan while a move to a lower plan is pending.' }),
88
+ lifted_by: z
89
+ .object({
90
+ plan: planId.nullable().meta({ description: 'The cheapest higher plan that raises this limit, or `null` when none does.' }),
91
+ addon: addonKey.nullable().meta({
92
+ description: 'The add-on that raises this limit, when the org\'s plan can buy add-ons; otherwise `null`.',
93
+ }),
94
+ })
95
+ .meta({ description: 'What would lift the limit.' }),
96
+ });
97
+ /**
98
+ * `planLimitDetails` plus the three numbers `assetStoreRefusedDetails`
99
+ * already carries, for the one refusal that answers both at once: a robot
100
+ * asset sync refused because the **org's** pool (not a per-robot ceiling,
101
+ * which no longer exists) has no room left. `store_bytes` and `used_bytes`
102
+ * are the org's totals, the same pool `max` and `used` above describe; they
103
+ * ride twice because the bridge reads these three specific keys off any
104
+ * `409` and a consumer parsing only `assetStoreRefusedDetails` must keep
105
+ * working unchanged.
106
+ *
107
+ * **The two carried-over fields are re-described, not re-typed.** Plain
108
+ * `.extend(assetStoreRefusedDetails.shape)` would publish that schema's own
109
+ * per-robot wording (`'The robot\'s store, in bytes.'`) on a refusal that is
110
+ * never about one robot — so `store_bytes` and `used_bytes` get their own
111
+ * `.meta()` here, org-wide, while keeping the exact same underlying type
112
+ * (`.meta()` clones a schema and only replaces its registered metadata, so
113
+ * the bridge's own parser sees the same shape it always did). `size_bytes`
114
+ * is unchanged: the refused upload's size means the same thing in both
115
+ * contexts.
116
+ */
117
+ export const assetPlanLimitDetails = planLimitDetails.extend({
118
+ ...assetStoreRefusedDetails.shape,
119
+ store_bytes: assetStoreRefusedDetails.shape.store_bytes.meta({
120
+ description: 'The org\'s asset pool, in bytes: `robots × asset_bytes_per_robot`.',
121
+ }),
122
+ used_bytes: assetStoreRefusedDetails.shape.used_bytes.meta({
123
+ description: 'Bytes the org\'s assets occupy, across every robot, before this upload.',
124
+ }),
125
+ });
126
+ /** The `details` of a `403 plan_required` refusal: the feature, the org's own plan, and the cheapest plan that has it (`requiredPlanFor`). */
127
+ export const planRequiredDetails = z.object({
128
+ feature: planFeature.meta({ description: 'The feature the action needs.' }),
129
+ plan: planId.meta({ description: 'The org\'s current plan.' }),
130
+ required_plan: planId.meta({ description: 'The cheapest plan that has the feature.' }),
131
+ });
132
+ /** The `details` of a `403 org_locked` refusal: why the org is locked, matching `orgLock.reason`. */
133
+ export const orgLockedDetails = z.object({
134
+ reason: z.enum(['payment', 'migration']).meta({
135
+ description: '`payment`: a payment is missing. `migration`: the org did not choose what stays when the beta ended.',
136
+ }),
137
+ });
66
138
  /**
67
139
  * The codes in use today. The wire deliberately allows any string — this
68
140
  * list is the shared vocabulary, not a closed set, so a new refusal never
@@ -891,4 +963,36 @@ export const ERROR_CODES = [
891
963
  * is no enumeration oracle.
892
964
  */
893
965
  'method_not_allowed',
966
+ // 2026-10-02 — plans (#103).
967
+ /**
968
+ * `409`: the action would push the org past one of its plan's limits —
969
+ * a seat, a robot, an app, an app user, this month's live video, or the
970
+ * org's asset storage pool (its robots × the plan's bytes per robot). Answered **before anything is written**, so a
971
+ * refused create or invite never leaves a half-made row behind. `details`
972
+ * is `planLimitDetails` (or `assetPlanLimitDetails` for an asset-storage
973
+ * refusal): which limit, how much is used, the ceiling, the plan that
974
+ * refused, and what would lift it — a higher plan, an add-on, or neither.
975
+ * The existing `quota_exceeded` protection ceiling is still checked, and
976
+ * only after this one: it exists to stop runaway consumption, not to tell
977
+ * a developer what their plan allows.
978
+ */
979
+ 'plan_limit',
980
+ /**
981
+ * `403`: the feature the caller reached for is not on the org's plan — an
982
+ * app's MCP endpoint, two-factor for a developer, a two-factor requirement
983
+ * for the org, an app's OIDC federation, a hosted logo, the audit export,
984
+ * or add-ons themselves. `details` is `planRequiredDetails`:
985
+ * the feature, the org's own plan, and the cheapest plan that has it, so
986
+ * the console can offer the upgrade in the same breath as the refusal.
987
+ */
988
+ 'plan_required',
989
+ /**
990
+ * `403`: the org is locked. Every sign-in and every token refresh is
991
+ * refused, except an owner's, so an owner can still sign in and settle
992
+ * it; the sign-in pages render this refusal. A bridge's `hello` is
993
+ * refused with this code as well. Nothing is deleted while an org is
994
+ * locked. `details` is `orgLockedDetails`, naming why — a missing
995
+ * payment, or the platform's own move off the beta.
996
+ */
997
+ 'org_locked',
894
998
  ];
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';