@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/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 exist on
9
- * `pro` only; see `ADDONS` and the `addons` feature.
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` only).
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`. Exist on `pro` only: on any other plan an
138
- * org's add-on counts are zero, and a plan change away from `pro` resets
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 restricted, while locked, to moving straight to Basic — narrower
332
- * still than the already-downward-only `PUT /api/org/plan/change`. `payment`
333
- * is a failed charge; `migration` is the platform's own move off the beta.
334
- * Either way the only plan a developer may choose while locked is Basic —
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`. Every field
486
- * is optional because this one route carries every shape of change an
487
- * operator makes to an org's plan — switching it, adding or removing
488
- * add-ons, overriding a limit, changing the billing currency, or moving the
489
- * period boundary — and a request that touched all of them at once would be
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 exist on
9
- * `pro` only; see `ADDONS` and the `addons` feature.
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
- asset_bytes_per_robot: limit,
43
- history_days: limit,
44
- audit_days: limit,
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` only).
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
- export const planFeatures = z.object(Object.fromEntries(planFeature.options.map((f) => [f, z.boolean()])));
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`. Exist on `pro` only: on any other plan an
76
- * org's add-on counts are zero, and a plan change away from `pro` resets
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. Meaningful on `pro` only
277
- * — see `planFeature.addons` — and zero on every other plan: a plan change
278
- * away from `pro` resets every count here to zero rather than leaving them
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: z.number().int().nonnegative(),
283
- robots: z.number().int().nonnegative(),
284
- apps: z.number().int().nonnegative(),
285
- app_user_packs: z.number().int().nonnegative(),
286
- live_video_packs: z.number().int().nonnegative(),
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: z.number().int().nonnegative(),
301
- robots: z.number().int().nonnegative(),
302
- apps: z.number().int().nonnegative(),
303
- app_users: z.number().int().nonnegative(),
304
- live_video_ms_this_month: z.number().int().nonnegative(),
305
- asset_bytes: z.number().int().nonnegative(),
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
- developers: z.array(z.uuid()),
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
- keep: planChangeKeep.nullable(),
356
- history_days_after: z.number().int().positive(),
357
- chosen_by: z.uuid(),
358
- chosen_at: z.iso.datetime(),
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 restricted, while locked, to moving straight to Basic — narrower
362
- * still than the already-downward-only `PUT /api/org/plan/change`. `payment`
363
- * is a failed charge; `migration` is the platform's own move off the beta.
364
- * Either way the only plan a developer may choose while locked is Basic —
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
- since: z.iso.datetime(),
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
- features: planFeatures,
394
- usage: orgPlanUsage,
395
- pending_change: pendingPlanChange.nullable(),
396
- lock: orgLock.nullable(),
397
- switch: z.object({
398
- at: z.iso.datetime().nullable(),
399
- needs_choice: z.boolean(),
400
- }).nullable(),
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) => [k, limit.optional()]))).strict();
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`. Every field
427
- * is optional because this one route carries every shape of change an
428
- * operator makes to an org's plan — switching it, adding or removing
429
- * add-ons, overriding a limit, changing the billing currency, or moving the
430
- * period boundary — and a request that touched all of them at once would be
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: an operator\'s plan, add-on or override ' +
2844
- 'change is never queued as a `pending_change`, unlike a developer\'s own downgrade. `409 plan_limit` when a lowered override or a ' +
2845
- 'removed add-on would leave the org\'s current usage over its new ceiling. Every change here is audited with the `fleetless` actor, ' +
2846
- 'never a developer\'s — the row names what an operator did, not who in the org asked for it.',
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.1",
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",