@fleetless/contracts 6.1.0-next.1 → 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.
- package/CHANGELOG.md +6 -4
- package/artifacts/openapi.json +112 -56
- package/artifacts/routes.json +1 -1
- package/artifacts/schema/addon-catalogue-entry.schema.json +12 -6
- package/artifacts/schema/admin-plan-change-request.schema.json +24 -6
- package/artifacts/schema/asset-plan-limit-details.schema.json +14 -7
- package/artifacts/schema/org-addons.schema.json +10 -5
- package/artifacts/schema/org-lock.schema.json +4 -2
- package/artifacts/schema/org-locked-details.schema.json +2 -1
- package/artifacts/schema/org-plan-usage.schema.json +12 -6
- package/artifacts/schema/org-plan.schema.json +100 -50
- package/artifacts/schema/pending-plan-change.schema.json +22 -11
- package/artifacts/schema/plan-catalogue-entry.schema.json +46 -23
- package/artifacts/schema/plan-change-keep.schema.json +8 -4
- package/artifacts/schema/plan-change-request.schema.json +12 -6
- package/artifacts/schema/plan-features.schema.json +14 -7
- package/artifacts/schema/plan-limit-details.schema.json +14 -7
- package/artifacts/schema/plan-limits.schema.json +16 -8
- package/artifacts/schema/plan-overrides.schema.json +8 -0
- package/artifacts/schema/plan-prices.schema.json +8 -4
- package/artifacts/schema/plan-required-details.schema.json +6 -3
- package/dist/errors.js +31 -21
- package/dist/plans.d.ts +15 -33
- package/dist/plans.js +128 -83
- package/dist/routes.js +5 -4
- package/package.json +1 -1
package/dist/plans.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { z } from 'zod';
|
|
|
5
5
|
*
|
|
6
6
|
* Four plans, cheapest first: `basic` (free), `plus`, `pro` and
|
|
7
7
|
* `enterprise` (sold by contract, never self-service — its limits and
|
|
8
|
-
* features are the catalogue's ceiling, not a priced row). Add-ons
|
|
9
|
-
*
|
|
8
|
+
* features are the catalogue's ceiling, not a priced row). Add-ons are
|
|
9
|
+
* bought on a plan with the `addons` feature; see `ADDONS`.
|
|
10
10
|
*/
|
|
11
11
|
export declare const planId: z.ZodEnum<{
|
|
12
12
|
basic: "basic";
|
|
@@ -55,7 +55,7 @@ export type PlanLimits = z.infer<typeof planLimits>;
|
|
|
55
55
|
* may force it org-wide), `app_oidc` (an app may federate sign-in to an
|
|
56
56
|
* external IdP), `hosted_logo` (a custom logo on the app's hosted pages),
|
|
57
57
|
* `audit_export` (the audit log's CSV export) and `addons` (whether add-ons
|
|
58
|
-
* may be bought at all — `pro`
|
|
58
|
+
* may be bought at all — `pro` and `enterprise`).
|
|
59
59
|
*/
|
|
60
60
|
export declare const planFeature: z.ZodEnum<{
|
|
61
61
|
require_two_factor: "require_two_factor";
|
|
@@ -134,9 +134,8 @@ export declare const planCatalogueEntry: z.ZodObject<{
|
|
|
134
134
|
export type PlanCatalogueEntry = z.infer<typeof planCatalogueEntry>;
|
|
135
135
|
/**
|
|
136
136
|
* What can be bought on top of a plan, each raising exactly one
|
|
137
|
-
* `planLimitKey` by `per_unit`.
|
|
138
|
-
*
|
|
139
|
-
* them to zero (the add-ons feature gate, `planFeature.addons`).
|
|
137
|
+
* `planLimitKey` by `per_unit`. Only a plan with the `addons` feature may
|
|
138
|
+
* buy them (`planFeature.addons`: `pro` and `enterprise`).
|
|
140
139
|
*/
|
|
141
140
|
export declare const addonKey: z.ZodEnum<{
|
|
142
141
|
apps: "apps";
|
|
@@ -217,12 +216,6 @@ export declare const planCurrency: z.ZodEnum<{
|
|
|
217
216
|
usd: "usd";
|
|
218
217
|
}>;
|
|
219
218
|
export type PlanCurrency = z.infer<typeof planCurrency>;
|
|
220
|
-
/**
|
|
221
|
-
* How many units of each add-on an org has bought. Meaningful on `pro` only
|
|
222
|
-
* — see `planFeature.addons` — and zero on every other plan: a plan change
|
|
223
|
-
* away from `pro` resets every count here to zero rather than leaving them
|
|
224
|
-
* stored and merely unread.
|
|
225
|
-
*/
|
|
226
219
|
export declare const orgAddons: z.ZodObject<{
|
|
227
220
|
seats: z.ZodNumber;
|
|
228
221
|
robots: z.ZodNumber;
|
|
@@ -231,17 +224,6 @@ export declare const orgAddons: z.ZodObject<{
|
|
|
231
224
|
live_video_packs: z.ZodNumber;
|
|
232
225
|
}, z.core.$strip>;
|
|
233
226
|
export type OrgAddons = z.infer<typeof orgAddons>;
|
|
234
|
-
/**
|
|
235
|
-
* What the org is using right now, counted the way each limit refuses
|
|
236
|
-
* against: `seats` is developers, owners included, plus pending team
|
|
237
|
-
* invitations; `app_users` is app users of every app plus pending app-user
|
|
238
|
-
* invitations; `live_video_ms_this_month` is this UTC calendar month's
|
|
239
|
-
* app-attributed live video only — a console session never counts and is
|
|
240
|
-
* never ended for it; `asset_bytes` is the sum of every robot's stored
|
|
241
|
-
* assets across the whole org. Read fresh on every call, the same discipline
|
|
242
|
-
* `GET /api/org/quotas` already keeps, so a number here is never one call
|
|
243
|
-
* behind the limit it is compared against.
|
|
244
|
-
*/
|
|
245
227
|
export declare const orgPlanUsage: z.ZodObject<{
|
|
246
228
|
seats: z.ZodNumber;
|
|
247
229
|
robots: z.ZodNumber;
|
|
@@ -328,10 +310,11 @@ export declare const pendingPlanChange: z.ZodObject<{
|
|
|
328
310
|
}, z.core.$strip>;
|
|
329
311
|
export type PendingPlanChange = z.infer<typeof pendingPlanChange>;
|
|
330
312
|
/**
|
|
331
|
-
* An org
|
|
332
|
-
*
|
|
333
|
-
* is a
|
|
334
|
-
* Either way the only plan
|
|
313
|
+
* An org that is locked: every sign-in and token refresh except an owner's
|
|
314
|
+
* answers `403 org_locked`, and its bridges are refused; nothing is
|
|
315
|
+
* deleted. `payment` is a missing payment; `migration` is the platform's
|
|
316
|
+
* own move off the beta. Either way the only plan an owner may choose
|
|
317
|
+
* while locked is Basic —
|
|
335
318
|
* see `target_state_conflict` with rule `locked_basic_only` on
|
|
336
319
|
* `PUT /api/org/plan/change`; a choice made while locked takes effect at
|
|
337
320
|
* once rather than waiting for the billing period to turn over.
|
|
@@ -482,12 +465,11 @@ export declare const planOverrides: z.ZodObject<{
|
|
|
482
465
|
}, z.core.$strict>;
|
|
483
466
|
export type PlanOverrides = z.infer<typeof planOverrides>;
|
|
484
467
|
/**
|
|
485
|
-
* What the operator sends on `PATCH /api/admin/orgs/:id/plan`.
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
* no easier to audit than four small ones in its place. `addons` is a
|
|
468
|
+
* What the operator sends on `PATCH /api/admin/orgs/:id/plan`. `plan` is
|
|
469
|
+
* required: every request names the plan the org ends up on, even when only
|
|
470
|
+
* an add-on, an override, the currency or the period end changes. Every
|
|
471
|
+
* other field is optional, so one route carries every shape of change an
|
|
472
|
+
* operator makes to an org's plan. `addons` is a
|
|
491
473
|
* partial `orgAddons`: only the counts named change, the rest are left as
|
|
492
474
|
* they are. `overrides` follows `planOverrides`: a key present with `null`
|
|
493
475
|
* clears that override.
|
package/dist/plans.js
CHANGED
|
@@ -5,8 +5,8 @@ import { z } from 'zod';
|
|
|
5
5
|
*
|
|
6
6
|
* Four plans, cheapest first: `basic` (free), `plus`, `pro` and
|
|
7
7
|
* `enterprise` (sold by contract, never self-service — its limits and
|
|
8
|
-
* features are the catalogue's ceiling, not a priced row). Add-ons
|
|
9
|
-
*
|
|
8
|
+
* features are the catalogue's ceiling, not a priced row). Add-ons are
|
|
9
|
+
* bought on a plan with the `addons` feature; see `ADDONS`.
|
|
10
10
|
*/
|
|
11
11
|
export const planId = z.enum(['basic', 'plus', 'pro', 'enterprise']);
|
|
12
12
|
/**
|
|
@@ -34,14 +34,18 @@ export const planLimitKey = z.enum([
|
|
|
34
34
|
/** `null` = by contract (Enterprise) or unlimited; see `orgPlan.limits`. */
|
|
35
35
|
const limit = z.number().int().positive().nullable();
|
|
36
36
|
export const planLimits = z.object({
|
|
37
|
-
seats: limit,
|
|
38
|
-
robots: limit,
|
|
39
|
-
apps: limit,
|
|
40
|
-
app_users: limit,
|
|
41
|
-
live_video_ms_per_month: limit
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
seats: limit.meta({ description: 'Developers, owners included, plus pending team invitations.' }),
|
|
38
|
+
robots: limit.meta({ description: 'Robots in the org.' }),
|
|
39
|
+
apps: limit.meta({ description: 'Apps in the org.' }),
|
|
40
|
+
app_users: limit.meta({ description: 'App users across every app of the org, plus pending app-user invitations.' }),
|
|
41
|
+
live_video_ms_per_month: limit.meta({
|
|
42
|
+
description: 'Live video watched by app users in one UTC calendar month, in milliseconds, across the org. Console sessions do not count.',
|
|
43
|
+
}),
|
|
44
|
+
asset_bytes_per_robot: limit.meta({
|
|
45
|
+
description: 'Asset storage per robot, in bytes (decimal: 1 GB = 1,000,000,000). The org stores up to robots × this value in total.',
|
|
46
|
+
}),
|
|
47
|
+
history_days: limit.meta({ description: 'Days the org\'s robot history is kept.' }),
|
|
48
|
+
audit_days: limit.meta({ description: 'Days the org\'s audit log is kept.' }),
|
|
45
49
|
});
|
|
46
50
|
/**
|
|
47
51
|
* What a plan unlocks beyond a number: `app_mcp` (the app's MCP server),
|
|
@@ -49,39 +53,47 @@ export const planLimits = z.object({
|
|
|
49
53
|
* may force it org-wide), `app_oidc` (an app may federate sign-in to an
|
|
50
54
|
* external IdP), `hosted_logo` (a custom logo on the app's hosted pages),
|
|
51
55
|
* `audit_export` (the audit log's CSV export) and `addons` (whether add-ons
|
|
52
|
-
* may be bought at all — `pro`
|
|
56
|
+
* may be bought at all — `pro` and `enterprise`).
|
|
53
57
|
*/
|
|
54
58
|
export const planFeature = z.enum(['app_mcp', 'two_factor', 'require_two_factor', 'app_oidc', 'hosted_logo', 'audit_export', 'addons']);
|
|
55
|
-
|
|
59
|
+
const FEATURE_DESCRIPTIONS = {
|
|
60
|
+
app_mcp: 'An app\'s own MCP endpoint, for its app users.',
|
|
61
|
+
two_factor: 'Two-factor sign-in for developers.',
|
|
62
|
+
require_two_factor: 'An owner may require two-factor sign-in for every developer of the org.',
|
|
63
|
+
app_oidc: 'An app may let its users sign in through an OpenID Connect identity provider.',
|
|
64
|
+
hosted_logo: 'An app\'s hosted pages show its logo and accent colour, not only its name.',
|
|
65
|
+
audit_export: 'The audit log can be exported as CSV.',
|
|
66
|
+
addons: 'Add-ons can be bought on top of the plan.',
|
|
67
|
+
};
|
|
68
|
+
export const planFeatures = z.object(Object.fromEntries(planFeature.options.map((f) => [f, z.boolean().meta({ description: FEATURE_DESCRIPTIONS[f] })])));
|
|
56
69
|
/** Integer cents, excluding VAT. */
|
|
57
70
|
const cents = z.number().int().nonnegative();
|
|
58
71
|
export const planPrices = z.object({
|
|
59
|
-
eur_month: cents,
|
|
60
|
-
usd_month: cents,
|
|
61
|
-
eur_year: cents,
|
|
62
|
-
usd_year: cents,
|
|
72
|
+
eur_month: cents.meta({ description: 'Per month in euro cents, excluding VAT.' }),
|
|
73
|
+
usd_month: cents.meta({ description: 'Per month in US dollar cents, excluding VAT: the euro price × 1.15, rounded up to a whole dollar.' }),
|
|
74
|
+
eur_year: cents.meta({ description: 'Per year in euro cents, excluding VAT: twelve months less 15 %.' }),
|
|
75
|
+
usd_year: cents.meta({ description: 'Per year in US dollar cents, excluding VAT: the yearly euro price × 1.15, rounded up to a whole dollar.' }),
|
|
63
76
|
});
|
|
64
77
|
export const planSupport = z.enum(['community', 'email', 'priority', 'named_contact']);
|
|
65
78
|
export const planCatalogueEntry = z.object({
|
|
66
|
-
id: planId,
|
|
79
|
+
id: planId.meta({ description: 'The plan.' }),
|
|
67
80
|
name: z.string().min(1).meta({ description: 'The plan\'s display name: Basic, Plus, Pro or Enterprise.' }),
|
|
68
|
-
limits: planLimits,
|
|
69
|
-
features: planFeatures,
|
|
81
|
+
limits: planLimits.meta({ description: 'What the plan allows. `null` means by contract.' }),
|
|
82
|
+
features: planFeatures.meta({ description: 'What the plan unlocks.' }),
|
|
70
83
|
prices: planPrices.nullable().meta({ description: '`null` for Enterprise: sold by contract, on request.' }),
|
|
71
|
-
support: planSupport,
|
|
84
|
+
support: planSupport.meta({ description: 'The support that comes with the plan.' }),
|
|
72
85
|
});
|
|
73
86
|
/**
|
|
74
87
|
* What can be bought on top of a plan, each raising exactly one
|
|
75
|
-
* `planLimitKey` by `per_unit`.
|
|
76
|
-
*
|
|
77
|
-
* them to zero (the add-ons feature gate, `planFeature.addons`).
|
|
88
|
+
* `planLimitKey` by `per_unit`. Only a plan with the `addons` feature may
|
|
89
|
+
* buy them (`planFeature.addons`: `pro` and `enterprise`).
|
|
78
90
|
*/
|
|
79
91
|
export const addonKey = z.enum(['seats', 'robots', 'apps', 'app_user_packs', 'live_video_packs']);
|
|
80
92
|
export const addonCatalogueEntry = z.object({
|
|
81
|
-
key: addonKey,
|
|
93
|
+
key: addonKey.meta({ description: 'The add-on.' }),
|
|
82
94
|
raises: planLimitKey.meta({ description: 'The plan limit this add-on raises.' }),
|
|
83
95
|
per_unit: z.number().int().positive().meta({ description: 'How much one unit of this add-on raises `raises` by.' }),
|
|
84
|
-
prices: planPrices,
|
|
96
|
+
prices: planPrices.meta({ description: 'The price of one unit.' }),
|
|
85
97
|
});
|
|
86
98
|
/**
|
|
87
99
|
* **No floating-point money, anywhere.** `eurCents * 1.15` would introduce
|
|
@@ -273,17 +285,17 @@ export function nextPlanRaising(plan, key) {
|
|
|
273
285
|
/** What an org is billed in. The catalogue's prices are fixed in both; this picks which one an invoice reads in. */
|
|
274
286
|
export const planCurrency = z.enum(['eur', 'usd']);
|
|
275
287
|
/**
|
|
276
|
-
* How many units of each add-on an org has bought.
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* stored and merely unread.
|
|
288
|
+
* How many units of each add-on an org has bought. Only a plan with the
|
|
289
|
+
* `addons` feature may buy them (see `planFeature.addons`); `orgPlan.limits`
|
|
290
|
+
* already includes what they add.
|
|
280
291
|
*/
|
|
292
|
+
const addonCount = z.number().int().nonnegative();
|
|
281
293
|
export const orgAddons = z.object({
|
|
282
|
-
seats:
|
|
283
|
-
robots:
|
|
284
|
-
apps:
|
|
285
|
-
app_user_packs:
|
|
286
|
-
live_video_packs:
|
|
294
|
+
seats: addonCount.meta({ description: 'Extra developer seats, one each.' }),
|
|
295
|
+
robots: addonCount.meta({ description: 'Extra robots, one each.' }),
|
|
296
|
+
apps: addonCount.meta({ description: 'Extra apps, one each.' }),
|
|
297
|
+
app_user_packs: addonCount.meta({ description: 'Packs of five extra app users.' }),
|
|
298
|
+
live_video_packs: addonCount.meta({ description: 'Packs of 250 extra hours of app-user live video per month.' }),
|
|
287
299
|
});
|
|
288
300
|
/**
|
|
289
301
|
* What the org is using right now, counted the way each limit refuses
|
|
@@ -296,13 +308,16 @@ export const orgAddons = z.object({
|
|
|
296
308
|
* `GET /api/org/quotas` already keeps, so a number here is never one call
|
|
297
309
|
* behind the limit it is compared against.
|
|
298
310
|
*/
|
|
311
|
+
const usageCount = z.number().int().nonnegative();
|
|
299
312
|
export const orgPlanUsage = z.object({
|
|
300
|
-
seats:
|
|
301
|
-
robots:
|
|
302
|
-
apps:
|
|
303
|
-
app_users:
|
|
304
|
-
live_video_ms_this_month:
|
|
305
|
-
|
|
313
|
+
seats: usageCount.meta({ description: 'Developers, owners included, plus pending team invitations.' }),
|
|
314
|
+
robots: usageCount.meta({ description: 'Robots in the org.' }),
|
|
315
|
+
apps: usageCount.meta({ description: 'Apps in the org.' }),
|
|
316
|
+
app_users: usageCount.meta({ description: 'App users across every app, plus pending app-user invitations.' }),
|
|
317
|
+
live_video_ms_this_month: usageCount.meta({
|
|
318
|
+
description: 'Live video watched by app users in the current UTC calendar month, in milliseconds. Console sessions do not count.',
|
|
319
|
+
}),
|
|
320
|
+
asset_bytes: usageCount.meta({ description: 'Bytes the assets of every robot of the org occupy, together.' }),
|
|
306
321
|
});
|
|
307
322
|
/**
|
|
308
323
|
* What a downward plan change keeps, by id, when the target plan cannot hold
|
|
@@ -315,10 +330,15 @@ export const orgPlanUsage = z.object({
|
|
|
315
330
|
* all — is refused at the door rather than silently ignored.
|
|
316
331
|
*/
|
|
317
332
|
export const planChangeKeep = z.object({
|
|
318
|
-
robots: z.array(z.uuid()),
|
|
319
|
-
apps: z.array(z.uuid()),
|
|
320
|
-
app_users: z.array(z.uuid())
|
|
321
|
-
|
|
333
|
+
robots: z.array(z.uuid()).meta({ description: 'The robots that stay, by id. Every other robot is deleted when the change takes effect.' }),
|
|
334
|
+
apps: z.array(z.uuid()).meta({ description: 'The apps that stay, by id. Every other app is deleted when the change takes effect.' }),
|
|
335
|
+
app_users: z.array(z.uuid()).meta({
|
|
336
|
+
description: 'The app users that stay, by id, across every app. Every other app user is deleted when the change takes effect.',
|
|
337
|
+
}),
|
|
338
|
+
developers: z.array(z.uuid()).meta({
|
|
339
|
+
description: 'The developers that stay, by user id, owners never among them: every owner stays. Every other developer is removed from the org ' +
|
|
340
|
+
'when the change takes effect.',
|
|
341
|
+
}),
|
|
322
342
|
}).strict();
|
|
323
343
|
/**
|
|
324
344
|
* Why a change is pending. `downgrade` and `cancel` (to Basic) are a
|
|
@@ -349,26 +369,35 @@ export const planChangeReason = z.enum(['downgrade', 'cancel', 'migration', 'loc
|
|
|
349
369
|
* lands.
|
|
350
370
|
*/
|
|
351
371
|
export const pendingPlanChange = z.object({
|
|
352
|
-
target_plan: planId,
|
|
353
|
-
reason: planChangeReason,
|
|
354
|
-
effective_at: z.iso.datetime().nullable()
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
372
|
+
target_plan: planId.meta({ description: 'The plan the org moves to.' }),
|
|
373
|
+
reason: planChangeReason.meta({ description: 'Why the change is pending: `downgrade`, `cancel`, `migration` or `lock`.' }),
|
|
374
|
+
effective_at: z.iso.datetime().nullable().meta({
|
|
375
|
+
description: 'When the change takes effect. `null` only for a move off the beta whose date is not set yet.',
|
|
376
|
+
}),
|
|
377
|
+
keep: planChangeKeep.nullable().meta({
|
|
378
|
+
description: 'What stays. `null` when the org already fits the target plan and nothing is deleted.',
|
|
379
|
+
}),
|
|
380
|
+
history_days_after: z.number().int().positive().meta({
|
|
381
|
+
description: 'Days of history and audit log the org keeps on the target plan.',
|
|
382
|
+
}),
|
|
383
|
+
chosen_by: z.uuid().meta({ description: 'The user id of the owner who chose the change, or the nil UUID when Fleetless queued it.' }),
|
|
384
|
+
chosen_at: z.iso.datetime().meta({ description: 'When the change was chosen.' }),
|
|
359
385
|
});
|
|
360
386
|
/**
|
|
361
|
-
* An org
|
|
362
|
-
*
|
|
363
|
-
* is a
|
|
364
|
-
* Either way the only plan
|
|
387
|
+
* An org that is locked: every sign-in and token refresh except an owner's
|
|
388
|
+
* answers `403 org_locked`, and its bridges are refused; nothing is
|
|
389
|
+
* deleted. `payment` is a missing payment; `migration` is the platform's
|
|
390
|
+
* own move off the beta. Either way the only plan an owner may choose
|
|
391
|
+
* while locked is Basic —
|
|
365
392
|
* see `target_state_conflict` with rule `locked_basic_only` on
|
|
366
393
|
* `PUT /api/org/plan/change`; a choice made while locked takes effect at
|
|
367
394
|
* once rather than waiting for the billing period to turn over.
|
|
368
395
|
*/
|
|
369
396
|
export const orgLock = z.object({
|
|
370
|
-
reason: z.enum(['payment', 'migration'])
|
|
371
|
-
|
|
397
|
+
reason: z.enum(['payment', 'migration']).meta({
|
|
398
|
+
description: '`payment`: a payment is missing. `migration`: the org did not choose what stays when the beta ended.',
|
|
399
|
+
}),
|
|
400
|
+
since: z.iso.datetime().meta({ description: 'When the org was locked.' }),
|
|
372
401
|
});
|
|
373
402
|
/**
|
|
374
403
|
* **The one read everything about an org's plan comes from**: the console's
|
|
@@ -381,23 +410,33 @@ export const orgLock = z.object({
|
|
|
381
410
|
* it still has to choose one.
|
|
382
411
|
*/
|
|
383
412
|
export const orgPlan = z.object({
|
|
384
|
-
plan: planId,
|
|
385
|
-
currency: planCurrency,
|
|
413
|
+
plan: planId.meta({ description: 'The org\'s current plan.' }),
|
|
414
|
+
currency: planCurrency.meta({ description: 'The currency the org\'s prices are shown and billed in.' }),
|
|
386
415
|
period_ends_at: z.iso.datetime().meta({
|
|
387
416
|
description: "The end of the organization's current billing period; while no payment period exists yet, the end of the current UTC calendar " +
|
|
388
417
|
'month. A pending downward plan change normally takes effect at this exact instant — except one chosen while the org was locked, ' +
|
|
389
418
|
"which lands at once, and the platform's own move off the beta, which lands at its switch date instead.",
|
|
390
419
|
}),
|
|
391
|
-
addons: orgAddons,
|
|
392
|
-
limits: planLimits
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
420
|
+
addons: orgAddons.meta({ description: 'The add-ons the org has bought. All zero on a plan without the `addons` feature.' }),
|
|
421
|
+
limits: planLimits.meta({
|
|
422
|
+
description: 'The org\'s effective limits: the plan\'s, raised by its add-ons, or an operator\'s override in their place. `null` means ' +
|
|
423
|
+
'unlimited for a count, and 90 days for `history_days` and `audit_days`.',
|
|
424
|
+
}),
|
|
425
|
+
features: planFeatures.meta({ description: 'What the org\'s plan unlocks.' }),
|
|
426
|
+
usage: orgPlanUsage.meta({ description: 'What the org uses now, counted the way each limit counts it.' }),
|
|
427
|
+
pending_change: pendingPlanChange.nullable().meta({ description: 'A move to a lower plan that has not taken effect yet, or `null`.' }),
|
|
428
|
+
lock: orgLock.nullable().meta({ description: 'Why and since when the org is locked, or `null` when it is not.' }),
|
|
429
|
+
switch: z
|
|
430
|
+
.object({
|
|
431
|
+
at: z.iso.datetime().nullable().meta({
|
|
432
|
+
description: 'When the org moves from the beta onto its plan. `null` while the date is not set.',
|
|
433
|
+
}),
|
|
434
|
+
needs_choice: z.boolean().meta({
|
|
435
|
+
description: 'Whether the org uses more than Basic allows, so an owner has to choose what stays before `at`.',
|
|
436
|
+
}),
|
|
437
|
+
})
|
|
438
|
+
.nullable()
|
|
439
|
+
.meta({ description: 'Set only while the org is still on the beta; `null` for every other org.' }),
|
|
401
440
|
});
|
|
402
441
|
/**
|
|
403
442
|
* What a developer may ask for on `PUT /api/org/plan/change` — **a downward
|
|
@@ -410,8 +449,10 @@ export const orgPlan = z.object({
|
|
|
410
449
|
* typo that `.strict()` would otherwise swallow in silence.
|
|
411
450
|
*/
|
|
412
451
|
export const planChangeRequest = z.object({
|
|
413
|
-
target_plan: planId,
|
|
414
|
-
keep: planChangeKeep.nullable()
|
|
452
|
+
target_plan: planId.meta({ description: 'The lower plan to move to; `basic` cancels.' }),
|
|
453
|
+
keep: planChangeKeep.nullable().meta({
|
|
454
|
+
description: 'What stays. `null` when the org already fits the target plan, so nothing is deleted.',
|
|
455
|
+
}),
|
|
415
456
|
}).strict();
|
|
416
457
|
/**
|
|
417
458
|
* An operator's per-org replacement for one or more catalogue limits —
|
|
@@ -421,22 +462,26 @@ export const planChangeRequest = z.object({
|
|
|
421
462
|
* plan's own number, which is why every value is nullable as well as
|
|
422
463
|
* optional — omitted and `null` are two different instructions.
|
|
423
464
|
*/
|
|
424
|
-
export const planOverrides = z.object(Object.fromEntries(planLimitKey.options.map((k) => [
|
|
465
|
+
export const planOverrides = z.object(Object.fromEntries(planLimitKey.options.map((k) => [
|
|
466
|
+
k,
|
|
467
|
+
limit.optional().meta({ description: `Replaces the plan's \`${k}\`. \`null\` clears the override.` }),
|
|
468
|
+
]))).strict();
|
|
425
469
|
/**
|
|
426
|
-
* What the operator sends on `PATCH /api/admin/orgs/:id/plan`.
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
* no easier to audit than four small ones in its place. `addons` is a
|
|
470
|
+
* What the operator sends on `PATCH /api/admin/orgs/:id/plan`. `plan` is
|
|
471
|
+
* required: every request names the plan the org ends up on, even when only
|
|
472
|
+
* an add-on, an override, the currency or the period end changes. Every
|
|
473
|
+
* other field is optional, so one route carries every shape of change an
|
|
474
|
+
* operator makes to an org's plan. `addons` is a
|
|
432
475
|
* partial `orgAddons`: only the counts named change, the rest are left as
|
|
433
476
|
* they are. `overrides` follows `planOverrides`: a key present with `null`
|
|
434
477
|
* clears that override.
|
|
435
478
|
*/
|
|
436
479
|
export const adminPlanChangeRequest = z.object({
|
|
437
|
-
plan: planId,
|
|
438
|
-
addons: orgAddons.partial().strict().optional(),
|
|
439
|
-
overrides: planOverrides.optional(),
|
|
440
|
-
currency: planCurrency.optional(),
|
|
441
|
-
period_ends_at: z.iso.datetime().nullable().optional()
|
|
480
|
+
plan: planId.meta({ description: 'The plan the org is on after this request.' }),
|
|
481
|
+
addons: orgAddons.partial().strict().optional().meta({ description: 'Add-on counts to set. Counts not named stay as they are.' }),
|
|
482
|
+
overrides: planOverrides.optional().meta({ description: 'Limits to override. `null` clears an override.' }),
|
|
483
|
+
currency: planCurrency.optional().meta({ description: 'The currency the org is billed in.' }),
|
|
484
|
+
period_ends_at: z.iso.datetime().nullable().optional().meta({
|
|
485
|
+
description: 'The end of the org\'s billing period. `null` falls back to the end of the current UTC month.',
|
|
486
|
+
}),
|
|
442
487
|
}).strict();
|
package/dist/routes.js
CHANGED
|
@@ -2840,10 +2840,11 @@ export const ROUTES = [
|
|
|
2840
2840
|
errors: ['unauthorized', 'not_found', 'validation_error', 'plan_limit', 'rate_limited'], transport: 'http',
|
|
2841
2841
|
notes: 'Every public host answers `404 not_found` for every `/api/admin/*` path — this route is reachable only on the cloud\'s private ' +
|
|
2842
2842
|
'address — and that same address answers `404` here too while `OPS_API_TOKEN` is not configured, so a door with no key behind it ' +
|
|
2843
|
-
'reads exactly like one nobody opened. An unknown `:id` is the same `404`. Applies at once
|
|
2844
|
-
'
|
|
2845
|
-
'
|
|
2846
|
-
'
|
|
2843
|
+
'reads exactly like one nobody opened. An unknown `:id` is the same `404`. Applies at once, never queued as a `pending_change`, ' +
|
|
2844
|
+
'and only when the org\'s current usage fits the result: `409 plan_limit` when a lower plan, a lowered override or a removed add-on ' +
|
|
2845
|
+
'would leave the org over a limit — the operator never deletes an org\'s things, only the owner\'s own choice does. On success it ' +
|
|
2846
|
+
'also withdraws any `pending_change`, ends a beta org\'s wait for the switch and lifts a lock. Audited as `org.plan_changed` with ' +
|
|
2847
|
+
'the `fleetless` actor, never a developer\'s — the row names what an operator did, not who in the org asked for it.',
|
|
2847
2848
|
},
|
|
2848
2849
|
/* ------------------------------------------------- assets (robot upload) */
|
|
2849
2850
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetless/contracts",
|
|
3
|
-
"version": "6.1.0-next.
|
|
3
|
+
"version": "6.1.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",
|