@saasicat/core 1.0.0-rc.6 → 1.0.0-rc.7

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/index.d.cts CHANGED
@@ -1,5 +1,17 @@
1
1
  /** Start of day (00:00 UTC) of the moment — for day-inclusive date comparisons. */
2
2
  declare function startOfUtcDay(date: Date): Date;
3
+ /**
4
+ * The day before `value` — how a validity window closes when its successor
5
+ * opens (`validUntil = successor.validFrom − 1 day`, the rule this module's
6
+ * header states and `startOfUtcDay` above is the reading half of).
7
+ *
8
+ * It lived as three separate expressions before 2026-08-27: one in each
9
+ * adapter's publish path and one in the bundle repository, all agreeing by
10
+ * coincidence rather than by construction. Day arithmetic rather than
11
+ * `− 24 * 60 * 60 * 1000`: the subtraction is only equivalent while the value
12
+ * is a UTC midnight, and nothing in the type says it is.
13
+ */
14
+ declare function previousUtcDay(value: Date): Date;
3
15
  /** `<=` upper bound for a date (structurally Prisma-compatible). */
4
16
  interface DateAtOrBefore {
5
17
  lte: Date;
@@ -73,256 +85,6 @@ type ActiveVersionWhereWithEndsAt = ActivePlanVersionWhereWithEndsAt;
73
85
  */
74
86
  declare const buildActiveVersionWhere: typeof buildActivePlanVersionWhere;
75
87
 
76
- type FeatureKey = string;
77
- type PlanId = string;
78
- type QuotaKey = string;
79
- interface FeatureDef {
80
- key: FeatureKey;
81
- label?: string;
82
- icon?: string;
83
- /** CORE / ADVANCED / PRO / BUSINESS / ENTERPRISE_ONLY — convention. */
84
- tier?: string;
85
- plannedOnly?: boolean;
86
- }
87
- interface PlanDef {
88
- id: PlanId;
89
- name?: string;
90
- tagline?: string;
91
- /** false = not selectable in self-service onboarding. Default: true. */
92
- marketed?: boolean;
93
- /** Highlighted card in onboarding (max. 1 per catalog). */
94
- popular?: boolean;
95
- /** Net monthly price. null = on request. */
96
- monthlyNet?: number | null;
97
- /** Net total amount per year. null = monthly only. */
98
- yearlyNet?: number | null;
99
- /** Map quotaKey → max value. -1 = unlimited. */
100
- quotas: Record<QuotaKey, number>;
101
- features: FeatureKey[];
102
- }
103
- /** App-wide marketing configuration. */
104
- interface PlanCatalogMarketing {
105
- /**
106
- * Allowed language pool that the app may market. First = default
107
- * locale. From it, the SuperAdmin activates a subset in the marketing
108
- * catalog (LocaleManager).
109
- */
110
- availableLocales: string[];
111
- }
112
- /**
113
- * App identity block for branding + version. Consumed by the `AdminPublicBootController`
114
- * and the `AdminManifestConfigFactory`; the SuperAdmin UI (platform
115
- * LoginPage, AdminLayout brand block) reads the same fields via PublicBoot.
116
- *
117
- * `name` = brand display name (e.g. "DemoApp", "ClubApp").
118
- * `label` = tag/subtitle in the brand block (e.g. "SuperAdmin").
119
- * `version` = app version string (build info).
120
- * `icon` = 2-character abbreviation for the logo badge (e.g. "ma", "da").
121
- * `logoUrl` = optional URL to a PNG/SVG; if set, the UI renders an <img>
122
- * instead of the initials badge.
123
- */
124
- interface PlanCatalogApp {
125
- name: string;
126
- label?: string;
127
- version?: string;
128
- icon?: string;
129
- logoUrl?: string;
130
- }
131
- interface PlanCatalog {
132
- schemaVersion: 1;
133
- projectKey: string;
134
- /** App identity (branding + version), see PlanCatalogApp. Optional. */
135
- app?: PlanCatalogApp;
136
- /** ISO-4217 currency code. */
137
- currency: string;
138
- /** VAT rate in percent. */
139
- vatRate: number;
140
- /** App-wide marketing configuration. Optional. */
141
- marketing?: PlanCatalogMarketing;
142
- features?: FeatureDef[];
143
- /**
144
- * Optional. When omitted, plans come exclusively from the
145
- * AdminUI / DB table (Plans/PlanVersions lifecycle).
146
- */
147
- plans?: PlanDef[];
148
- }
149
-
150
- /** Backend capability key, convention: domain.action[.action]. */
151
- type CapabilityKey = string;
152
- /** Frontend action-registry key. Same convention as CapabilityKey. */
153
- type ActionKey = CapabilityKey;
154
- /** Lookup key in the static extensions: map of the UI build. */
155
- type ComponentKey = string;
156
- interface AdminManifest {
157
- schemaVersion: 1;
158
- project: {
159
- key: string;
160
- displayName: string;
161
- /** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
162
- label?: string;
163
- /** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
164
- icon?: string;
165
- logoUrl?: string;
166
- environment?: 'production' | 'staging' | 'development';
167
- /**
168
- * Allowed locale pool from the app config (`saas.yaml`
169
- * `marketing.availableLocales`). First = default..
170
- */
171
- availableLocales?: string[];
172
- /** Default locale; equals `availableLocales[0]`. */
173
- defaultLocale?: string;
174
- };
175
- build: {
176
- platformPackageVersion: string;
177
- appVersion: string;
178
- manifestHash: string;
179
- };
180
- planCatalogSnapshot: {
181
- source: string;
182
- hash: string;
183
- currency: string;
184
- vatRate: number;
185
- features?: FeatureDef[];
186
- plans: PlanDef[];
187
- };
188
- /** Map CapabilityKey → boolean. Manifest is never a security source. */
189
- capabilities: Record<CapabilityKey, boolean>;
190
- navigation: {
191
- standardPages: Partial<Record<StandardPageKey, StandardPageDef>>;
192
- projectPages?: ProjectPageDef[];
193
- };
194
- dashboard?: {
195
- kpiCards?: KpiCardDef[];
196
- };
197
- tenants?: {
198
- columns?: TenantColumnDef[];
199
- actions?: TenantActionDef[];
200
- };
201
- audit?: {
202
- actions?: AuditActionDef[];
203
- };
204
- }
205
- type StandardPageKey = 'dashboard' | 'tenants' | 'subscriptions' | 'promoCodes' | 'plans' | 'audit' | 'users' | 'pilots' | 'discovery' | 'bundles' | 'marketingCatalog' | 'platformEmail' | 'platformEmailHistory';
206
- interface StandardPageDef {
207
- enabled: boolean;
208
- requiredCapability?: CapabilityKey;
209
- }
210
- interface ProjectPageDef {
211
- /** `<projectKey>.<area>`, e.g. `demoapp.datev`. */
212
- id: string;
213
- label: string;
214
- icon?: string;
215
- /** Frontend route, e.g. `/admin/datev`. */
216
- route: string;
217
- navSection?: string;
218
- /** Lookup in the static extensions: map of the shell build. */
219
- componentKey: ComponentKey;
220
- requiredCapability?: CapabilityKey;
221
- prefetchOnIdle?: boolean;
222
- }
223
- interface KpiCardDef {
224
- id: string;
225
- label: string;
226
- /** Required path: /api/v1/admin/(extras|dashboard)/... */
227
- endpoint: string;
228
- displayHint: KpiDisplayHint;
229
- /** 0–100; UI sorts descending. */
230
- slotPriority?: number;
231
- requiredCapability?: CapabilityKey;
232
- }
233
- interface KpiDisplayHint {
234
- type: 'value' | 'value+timestamp' | 'value+spark8w' | 'value+delta';
235
- icon?: string;
236
- }
237
- interface TenantColumnDef {
238
- key: string;
239
- label: string;
240
- /** Required path: /api/v1/admin/extras/...; MUST be batch-capable, no {slug}/{tenantId}. */
241
- endpoint: string;
242
- requiredCapability?: CapabilityKey;
243
- }
244
- interface TenantActionDef {
245
- /** `<projectKey>.<area>.<verb>`, e.g. `demoapp.datev.runExport`. */
246
- id: string;
247
- label: string;
248
- /** Lookup in the static actions: map of the shell build. */
249
- actionKey: ActionKey;
250
- requiredCapability?: CapabilityKey;
251
- requiresMfa?: boolean;
252
- confirmType?: 'none' | 'simple' | 'typed-slug' | 'typed-production' | 'date';
253
- }
254
- interface AuditActionDef {
255
- /** SCREAMING_SNAKE_CASE; matched to the AuditLog.action column. */
256
- key: string;
257
- label: string;
258
- severity?: 'info' | 'low' | 'medium' | 'high';
259
- }
260
- interface ManifestContribution {
261
- capabilities?: Record<CapabilityKey, boolean>;
262
- navigation?: {
263
- standardPages?: Partial<Record<StandardPageKey, StandardPageDef>>;
264
- projectPages?: ProjectPageDef[];
265
- };
266
- dashboard?: {
267
- kpiCards?: KpiCardDef[];
268
- };
269
- tenants?: {
270
- columns?: TenantColumnDef[];
271
- actions?: TenantActionDef[];
272
- };
273
- audit?: {
274
- actions?: AuditActionDef[];
275
- };
276
- }
277
- interface PublicBootResponse {
278
- project: {
279
- key: string;
280
- displayName: string;
281
- /** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
282
- label?: string;
283
- /** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
284
- icon?: string;
285
- logoUrl?: string;
286
- environment?: 'production' | 'staging' | 'development';
287
- };
288
- }
289
-
290
- /** Format: 'web:<email>:<sessionId>' or 'cli:<email>:<host>'. */
291
- type ActorTag = string;
292
- interface AuditEntry {
293
- id: string;
294
- /** null = platform action without tenant context (SUPER_ADMIN). */
295
- tenantId: string | null;
296
- /** null = system / cron-triggered. */
297
- userId: string | null;
298
- /** Convenience field; backend resolves it from userId. */
299
- userEmail: string | null;
300
- /** e.g. 'Tenant', 'PromoCode', 'Subscription', 'PlanVersion', 'User'. */
301
- entity: string;
302
- entityId: string;
303
- /** SCREAMING_SNAKE_CASE; past-tense oriented. */
304
- action: string;
305
- /** Freely structured. Convention: { field: { old, new } } or { reason, ... }. */
306
- changes: Record<string, unknown> | null;
307
- actorTag: ActorTag | null;
308
- ipAddress: string | null;
309
- userAgent: string | null;
310
- createdAt: string;
311
- }
312
- interface AuditQuery {
313
- tenantId?: string;
314
- userId?: string;
315
- entity?: string;
316
- entityId?: string;
317
- action?: string;
318
- /** Wildcard-capable, e.g. 'cli:*'. */
319
- actorTag?: string;
320
- from?: string;
321
- to?: string;
322
- page?: number;
323
- pageSize?: number;
324
- }
325
-
326
88
  /**
327
89
  * Approval lifecycle of a feature or a quota. Approval happens per
328
90
  * FEATURE/QUOTA — not per capability (#20); only `approved` entries
@@ -385,7 +147,6 @@ type CatalogEntryI18n = Record<string, CatalogEntryI18nFields>;
385
147
  */
386
148
  interface CapabilityCatalogEntryRow {
387
149
  id: string;
388
- projectKey: string;
389
150
  capabilityKey: string;
390
151
  label: string;
391
152
  description: string | null;
@@ -422,7 +183,6 @@ type FeatureTier = 'CORE' | 'ADVANCED' | 'PRO' | 'ENTERPRISE' | string;
422
183
  */
423
184
  interface FeatureCatalogEntryRow {
424
185
  id: string;
425
- projectKey: string;
426
186
  featureKey: string;
427
187
  label: string;
428
188
  description: string | null;
@@ -477,7 +237,6 @@ interface FeatureCatalogEntryRow {
477
237
  */
478
238
  interface QuotaCatalogEntryRow {
479
239
  id: string;
480
- projectKey: string;
481
240
  quotaKey: string;
482
241
  label: string;
483
242
  description: string | null;
@@ -521,7 +280,6 @@ interface QuotaCatalogEntryRow {
521
280
  * matching field is relevant.
522
281
  */
523
282
  interface CatalogEntryFilter {
524
- projectKey: string;
525
283
  discoveryStatus?: DiscoveryStatus;
526
284
  codeStatus?: CapabilityCodeStatus;
527
285
  }
@@ -635,7 +393,6 @@ declare const MARKETING_PRIORITY_MAX = 10000;
635
393
  */
636
394
  interface MarketingProjectionRow {
637
395
  id: string;
638
- projectKey: string;
639
396
  targetType: MarketingTargetType;
640
397
  targetVersionId: string;
641
398
  /** ISO-639-1, optionally with region suffix (`de`, `en`, `de-AT`). */
@@ -680,15 +437,13 @@ interface MarketingProjectionRow {
680
437
  createdAt: string;
681
438
  updatedAt: string;
682
439
  }
683
- /** Filter for `MarketingProjectionRepository.list()`. At least projectKey. */
440
+ /** Filter for `MarketingProjectionRepository.list()`. */
684
441
  interface MarketingProjectionFilter {
685
- projectKey: string;
686
442
  targetType?: MarketingTargetType;
687
443
  targetVersionId?: string;
688
444
  locale?: string;
689
445
  }
690
446
  interface CreateMarketingProjectionData {
691
- projectKey: string;
692
447
  targetType: MarketingTargetType;
693
448
  targetVersionId: string;
694
449
  locale?: string;
@@ -718,14 +473,87 @@ interface UpdateMarketingProjectionData {
718
473
  highlight?: boolean;
719
474
  }
720
475
 
721
- type PromoCodeValueType = 'PERCENT' | 'ABSOLUTE';
722
- type PromoCodeDurationType = 'ONCE' | 'MONTHS' | 'BILLING_CYCLES';
723
- type PromoCodeStatus = 'ACTIVE' | 'PAUSED' | 'EXHAUSTED' | 'EXPIRED';
724
- type PromoCodeRedemptionStatus = 'ACTIVE' | 'REVERSED' | 'EXPIRED';
725
- type BillingCycle = 'MONTHLY' | 'YEARLY';
726
- type PromoCodeValidationResult = 'VALID' | 'EXPIRED' | 'EXHAUSTED' | 'NOT_FOUND' | 'PAUSED' | 'NOT_APPLICABLE' | 'FIRST_TIME_ONLY' | 'ZERO_INVOICE_BLOCKED' | 'MIN_AMOUNT_NOT_REACHED' | 'RATE_LIMITED';
727
- interface CreatePromoCodeRequest {
728
- /** A–Z, 0–9, '-' and '_'; 4–32 characters; case-insensitive, stored in UPPER. */
476
+ type FeatureKey = string;
477
+ type PlanId = string;
478
+ type QuotaKey = string;
479
+ interface FeatureDef {
480
+ key: FeatureKey;
481
+ label?: string;
482
+ icon?: string;
483
+ /** CORE / ADVANCED / PRO / BUSINESS / ENTERPRISE_ONLY — convention. */
484
+ tier?: string;
485
+ plannedOnly?: boolean;
486
+ }
487
+ interface PlanDef {
488
+ id: PlanId;
489
+ name?: string;
490
+ tagline?: string;
491
+ /** false = not selectable in self-service onboarding. Default: true. */
492
+ marketed?: boolean;
493
+ /** Highlighted card in onboarding (max. 1 per catalog). */
494
+ popular?: boolean;
495
+ /** Net monthly price. null = on request. */
496
+ monthlyNet?: number | null;
497
+ /** Net total amount per year. null = monthly only. */
498
+ yearlyNet?: number | null;
499
+ /** Map quotaKey → max value. -1 = unlimited. */
500
+ quotas: Record<QuotaKey, number>;
501
+ features: FeatureKey[];
502
+ }
503
+ /** App-wide marketing configuration. */
504
+ interface PlanCatalogMarketing {
505
+ /**
506
+ * Allowed language pool that the app may market. First = default
507
+ * locale. From it, the SuperAdmin activates a subset in the marketing
508
+ * catalog (LocaleManager).
509
+ */
510
+ availableLocales: string[];
511
+ }
512
+ /**
513
+ * App identity block for branding + version. Consumed by the `AdminPublicBootController`
514
+ * and the `AdminManifestConfigFactory`; the SuperAdmin UI (platform
515
+ * LoginPage, AdminLayout brand block) reads the same fields via PublicBoot.
516
+ *
517
+ * `name` = brand display name (e.g. "DemoApp", "ClubApp").
518
+ * `label` = tag/subtitle in the brand block (e.g. "SuperAdmin").
519
+ * `version` = app version string (build info).
520
+ * `icon` = 2-character abbreviation for the logo badge (e.g. "ma", "da").
521
+ * `logoUrl` = optional URL to a PNG/SVG; if set, the UI renders an <img>
522
+ * instead of the initials badge.
523
+ */
524
+ interface PlanCatalogApp {
525
+ name: string;
526
+ label?: string;
527
+ version?: string;
528
+ icon?: string;
529
+ logoUrl?: string;
530
+ }
531
+ interface PlanCatalog {
532
+ schemaVersion: 1;
533
+ /** App identity (branding + version), see PlanCatalogApp. */
534
+ app: PlanCatalogApp;
535
+ /** ISO-4217 currency code. */
536
+ currency: string;
537
+ /** VAT rate in percent. */
538
+ vatRate: number;
539
+ /** App-wide marketing configuration. Optional. */
540
+ marketing?: PlanCatalogMarketing;
541
+ features?: FeatureDef[];
542
+ /**
543
+ * Optional. When omitted, plans come exclusively from the
544
+ * AdminUI / DB table (Plans/PlanVersions lifecycle).
545
+ */
546
+ plans?: PlanDef[];
547
+ }
548
+
549
+ type PromoCodeValueType = 'PERCENT' | 'ABSOLUTE';
550
+ type PromoCodeDurationType = 'ONCE' | 'MONTHS' | 'BILLING_CYCLES';
551
+ type PromoCodeStatus = 'ACTIVE' | 'PAUSED' | 'EXHAUSTED' | 'EXPIRED';
552
+ type PromoCodeRedemptionStatus = 'ACTIVE' | 'REVERSED' | 'EXPIRED';
553
+ type BillingCycle = 'MONTHLY' | 'YEARLY';
554
+ type PromoCodeValidationResult = 'VALID' | 'EXPIRED' | 'EXHAUSTED' | 'NOT_FOUND' | 'PAUSED' | 'NOT_APPLICABLE' | 'FIRST_TIME_ONLY' | 'ZERO_INVOICE_BLOCKED' | 'MIN_AMOUNT_NOT_REACHED' | 'RATE_LIMITED';
555
+ interface CreatePromoCodeRequest {
556
+ /** A–Z, 0–9, '-' and '_'; 4–32 characters; case-insensitive, stored in UPPER. */
729
557
  code: string;
730
558
  valueType: PromoCodeValueType;
731
559
  /** For PERCENT: 0.01–100. For ABSOLUTE: > 0 in catalog currency. */
@@ -914,7 +742,7 @@ interface VersionedEntityBase {
914
742
  * own migration).
915
743
  * - `startedAt` is the contract start of this booking.
916
744
  * - `minimumTermEndsAt` = end of the minimum term; `null` = no minimum term
917
- * (platform default = 12 months, set service-side).
745
+ * (platform default = no commitment, set service-side).
918
746
  * - `canceledAt` / `canceledEffectiveAt`: cancellation anchor vs. effective
919
747
  * date. Before the minimum term ends, `canceledEffectiveAt =
920
748
  * minimumTermEndsAt`, otherwise the subscription's period end.
@@ -931,6 +759,18 @@ interface SubscriptionBundleRecord {
931
759
  minimumTermEndsAt: Date | null;
932
760
  canceledAt: Date | null;
933
761
  canceledEffectiveAt: Date | null;
762
+ /**
763
+ * The rhythm this booking is billed in, and the window it is billed for.
764
+ *
765
+ * A bundle's periods end on the day its plan's do — the first one short,
766
+ * from the booking to the next occurrence of that day, and every one after
767
+ * it anchor to anchor. Null on a booking made before these fields existed,
768
+ * or on one whose plan has no period; readers fall back to the plan's
769
+ * cycle, which is what every booking used before.
770
+ */
771
+ billingCycle: string | null;
772
+ currentPeriodStart: Date | null;
773
+ currentPeriodEnd: Date | null;
934
774
  createdAt: Date;
935
775
  updatedAt: Date;
936
776
  }
@@ -944,14 +784,27 @@ interface SubscriptionBundleRecord {
944
784
  interface SubscriptionBundleView extends SubscriptionBundleRecord {
945
785
  bundleKey: string | null;
946
786
  label: string | null;
947
- monthlyNet: string | null;
787
+ /**
788
+ * What this booking is billed at, in the rhythm it was booked in and with
789
+ * the plan's pricing override applied.
790
+ *
791
+ * It was `monthlyNet` until 2026-08-27 and carried the bundle's base
792
+ * monthly price whatever the booking was — so a yearly booking of a bundle
793
+ * priced 10 monthly and 100 yearly reported 10. The name was half the
794
+ * defect: a field called `monthlyNet` on a yearly booking cannot be right.
795
+ */
796
+ priceNet: number | null;
948
797
  }
949
798
  interface CreateSubscriptionBundleData {
950
799
  subscriptionId: string;
951
800
  bundleVersionId: string;
952
801
  startedAt: Date;
953
- /** Default = startedAt + 12 months, unless set. */
802
+ /** Null unless a commitment was configured or asked for. */
954
803
  minimumTermEndsAt?: Date | null;
804
+ /** The rhythm and window worked out above this port. */
805
+ billingCycle?: string | null;
806
+ currentPeriodStart?: Date | null;
807
+ currentPeriodEnd?: Date | null;
955
808
  }
956
809
  interface CancelSubscriptionBundleData {
957
810
  canceledAt: Date;
@@ -1000,7 +853,6 @@ interface BundlePricingOverride {
1000
853
  */
1001
854
  interface BundleRow {
1002
855
  id: string;
1003
- projectKey: string;
1004
856
  bundleKey: string;
1005
857
  label: string;
1006
858
  description: string | null;
@@ -1039,7 +891,6 @@ interface BundleVersionRow extends VersionedEntityBase {
1039
891
  * first BundleVersion via `CreateBundleVersionDraftData`.
1040
892
  */
1041
893
  interface CreateBundleData {
1042
- projectKey: string;
1043
894
  bundleKey: string;
1044
895
  label: string;
1045
896
  description?: string | null;
@@ -1048,9 +899,9 @@ interface CreateBundleData {
1048
899
  i18n?: CatalogEntryI18n;
1049
900
  }
1050
901
  /**
1051
- * Fields that may be changed on the bundle master. `bundleKey` and
1052
- * `projectKey` are intentionally not here — master identity is immutable;
1053
- * whoever wants to change them creates a new bundle and retires the old one.
902
+ * Fields that may be changed on the bundle master. `bundleKey` is
903
+ * intentionally not here — master identity is immutable; whoever wants to
904
+ * change it creates a new bundle and retires the old one.
1054
905
  */
1055
906
  interface UpdateBundleData {
1056
907
  label?: string;
@@ -1092,97 +943,354 @@ interface CreateBundleVersionDraftData {
1092
943
  validUntil?: string | null;
1093
944
  createdByUserId?: string | null;
1094
945
  }
1095
- /**
1096
- * Fields of a draft BundleVersion that may still be changed.
1097
- * With also for published-but-future versions
1098
- * (latest-in-chain, 0 subs, validFrom > now) — see
1099
- * `isVersionEditable`.
1100
- */
1101
- interface UpdateBundleVersionDraftData {
1102
- features?: FeatureKey[];
1103
- quotas?: Record<QuotaKey, number>;
1104
- compatibility?: BundleCompatibility;
1105
- pricingOverrides?: BundlePricingOverride[];
1106
- monthlyNet?: string | null;
1107
- yearlyNet?: string | null;
1108
- marketed?: boolean;
1109
- changeNote?: string;
1110
- /**
1111
- * New `validFrom` for the version. Freely settable when updating a
1112
- * draft; for a published-but-future version the new date must still
1113
- * lie in the future — the service checks that with
1114
- * `isVersionEditable` against the freshly loaded state.
1115
- */
1116
- validFrom?: string | null;
1117
- validUntil?: string | null;
946
+ /**
947
+ * Fields of a draft BundleVersion that may still be changed.
948
+ * With also for published-but-future versions
949
+ * (latest-in-chain, 0 subs, validFrom > now) — see
950
+ * `isVersionEditable`.
951
+ */
952
+ interface UpdateBundleVersionDraftData {
953
+ features?: FeatureKey[];
954
+ quotas?: Record<QuotaKey, number>;
955
+ compatibility?: BundleCompatibility;
956
+ pricingOverrides?: BundlePricingOverride[];
957
+ monthlyNet?: string | null;
958
+ yearlyNet?: string | null;
959
+ marketed?: boolean;
960
+ changeNote?: string;
961
+ /**
962
+ * New `validFrom` for the version. Freely settable when updating a
963
+ * draft; for a published-but-future version the new date must still
964
+ * lie in the future — the service checks that with
965
+ * `isVersionEditable` against the freshly loaded state.
966
+ */
967
+ validFrom?: string | null;
968
+ validUntil?: string | null;
969
+ }
970
+ /**
971
+ * Input for `publishBundleVersion()`. `nonRegressive` and
972
+ * `publishedChanges` are computed by the service from the diff against the
973
+ * predecessor version; the caller supplies only confirmation
974
+ * + user tag + validity dates.
975
+ *
976
+ * `validFrom` is **required** at publish (analogous to `PublishPlanVersionData`).
977
+ * If the draft already carries a `validFrom`, it is optional here. The
978
+ * service validates strictly > `validFrom` of the predecessor version and sets
979
+ * its `validUntil` via auto succession to `validFrom - 1 day`.
980
+ */
981
+ interface PublishBundleVersionData {
982
+ publishedByUserId: string | null;
983
+ /**
984
+ * If true and the diff classifies the version as regressive,
985
+ * it is published anyway — relevant for bulk-publish MFA confirmation
986
+ * ( editor-UI obligations).
987
+ */
988
+ forceRegressive?: boolean;
989
+ /**
990
+ * Allows publish despite an explicit price of 0.00. Default false: an
991
+ * explicit 0.00 publish is blocked (protection against seed placeholders).
992
+ * `null` prices (override resolution) are not affected.
993
+ */
994
+ allowZeroPrice?: boolean;
995
+ /**
996
+ * Required at publish if the draft has no `validFrom`. Must lie
997
+ * strictly after `validFrom` of the predecessor version. ISO-8601
998
+ * (`YYYY-MM-DD` or full timestamp).
999
+ */
1000
+ validFrom?: string | null;
1001
+ /**
1002
+ * Optional; null = valid indefinitely (fits the last version
1003
+ * of a bundle). When a successor version is created it is automatically
1004
+ * overwritten by the service.
1005
+ */
1006
+ validUntil?: string | null;
1007
+ }
1008
+ /**
1009
+ * Code of a strict-mode violation. lists the eight rules
1010
+ * that are checked; each rule has its own code so the UI can show
1011
+ * focused help texts.
1012
+ */
1013
+ type StrictModeWarningCode = 'CAPABILITY_MISSING' | 'CAPABILITY_RETIRED' | 'FEATURE_MISSING' | 'FEATURE_PLANNED_ONLY' | 'BUNDLE_FEATURE_UNKNOWN' | 'BUNDLE_PLAN_KEY_UNKNOWN' | 'PLAN_FEATURE_UNKNOWN' | 'PLAN_FEATURE_NOT_APPROVED' | 'BUNDLE_FEATURE_NOT_APPROVED' | 'PLAN_FEATURE_DEPENDENCY_UNSATISFIED' | 'BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED' | 'QUOTA_MISSING' | 'QUOTA_NOT_APPROVED' | 'VERSION_PUBLISH_OVERLAP';
1014
+ /**
1015
+ * A strict-mode violation. `field` points to the violating field
1016
+ * (e.g. `'features[3]'`), `value` is the concrete value (e.g. `'INVENTORY'`).
1017
+ */
1018
+ interface StrictModeWarning {
1019
+ code: StrictModeWarningCode;
1020
+ /** Human-readable reason (German). */
1021
+ message: string;
1022
+ /** Path to the violating field; optional. */
1023
+ field?: string;
1024
+ /** The concrete violating value; optional. */
1025
+ value?: string;
1026
+ }
1027
+ /**
1028
+ * Service result for mutating Bundle operations
1029
+ * (createDraft, updateDraft, publish): returns the persisted row plus
1030
+ * a list of strict-mode warnings. In `warn-only` mode the
1031
+ * warnings go into the UI as a banner; in `blocking` mode the service throws
1032
+ * HTTP 422 instead, with the same warning list as the body.
1033
+ */
1034
+ interface BundleVersionMutationResult {
1035
+ bundleVersion: BundleVersionRow;
1036
+ warnings: StrictModeWarning[];
1037
+ }
1038
+
1039
+ /**
1040
+ * The column values a new BundleVersion draft starts from.
1041
+ *
1042
+ * Every adapter has to apply the same defaults — an absent quota map is `{}`,
1043
+ * an absent price is null rather than zero, an unstated `marketed` is true —
1044
+ * and two adapters spelling that out separately is the same decision written
1045
+ * twice. It is also the variant jscpd does catch, which is how this came out:
1046
+ * `adapter-drizzle` learning about bundles put a second copy beside
1047
+ * `adapter-prisma`'s.
1048
+ *
1049
+ * Validity windows are deliberately absent. Whether a draft carries
1050
+ * `validFrom`/`validUntil` is an adapter capability rather than a default, and
1051
+ * an adapter that does not maintain those columns must not write them.
1052
+ */
1053
+ declare function bundleDraftDefaults(data: CreateBundleVersionDraftData): {
1054
+ baseVersionId: string | null;
1055
+ features: string[];
1056
+ quotas: Record<string, number>;
1057
+ compatibility: Record<string, unknown>;
1058
+ pricingOverrides: unknown[];
1059
+ monthlyNet: string | null;
1060
+ yearlyNet: string | null;
1061
+ marketed: boolean;
1062
+ changeNote: string;
1063
+ createdByUserId: string | null;
1064
+ };
1065
+ /**
1066
+ * The column values a new Bundle stem starts from.
1067
+ *
1068
+ * The same defaulting rule as above, one level up: an absent description or
1069
+ * icon is null rather than an empty string, an unstated sort order is 0, an
1070
+ * absent translation map is `{}`. Written out in five places before this — two
1071
+ * adapters and two fakes — which is four opportunities for one of them to
1072
+ * decide differently.
1073
+ */
1074
+ declare function bundleStemDefaults(data: CreateBundleData): {
1075
+ bundleKey: string;
1076
+ label: string;
1077
+ description: string | null;
1078
+ icon: string | null;
1079
+ sortOrder: number;
1080
+ i18n: CatalogEntryI18n;
1081
+ };
1082
+ /** The stored shape both adapters read a bundle stem back from. */
1083
+ interface StoredBundleStem {
1084
+ id: string;
1085
+ bundleKey: string;
1086
+ label: string;
1087
+ description: string | null;
1088
+ icon: string | null;
1089
+ sortOrder: number;
1090
+ i18n: unknown;
1091
+ createdAt: Date;
1092
+ updatedAt: Date;
1093
+ deletedAt: Date | null;
1094
+ }
1095
+ /**
1096
+ * A stored bundle stem as the port describes it.
1097
+ *
1098
+ * The two stores spell the columns identically, so the mapping was identical
1099
+ * too — and an identical mapping in two files is one place for a field to be
1100
+ * forgotten when the row grows. `i18n` arrives as JSON of unknown shape from
1101
+ * both, and a non-object becomes `{}` rather than reaching a caller that
1102
+ * expects a map.
1103
+ */
1104
+ declare function toBundleStemRow(row: StoredBundleStem): BundleRow;
1105
+ /**
1106
+ * The fields a caller actually gave, as a patch.
1107
+ *
1108
+ * The update DTOs in this codebase mean three different things by three
1109
+ * different values: a value changes the column, an explicit `null` clears it,
1110
+ * and an **omitted** field leaves it alone. Only the last one needs care, and
1111
+ * it was written out as `...(data.x !== undefined ? { x: data.x } : {})` more
1112
+ * than fifty times across five repositories — one decision, fifty
1113
+ * opportunities to spell it differently, and the duplication ratchet is what
1114
+ * finally pointed at it.
1115
+ *
1116
+ * `null` is deliberately kept: it is a value a caller chose, not an absence.
1117
+ */
1118
+ declare function definedFields<T extends object, K extends keyof T>(data: T, keys: readonly K[]): Partial<Pick<T, K>>;
1119
+
1120
+ /** Backend capability key, convention: domain.action[.action]. */
1121
+ type CapabilityKey = string;
1122
+ /** Frontend action-registry key. Same convention as CapabilityKey. */
1123
+ type ActionKey = CapabilityKey;
1124
+ /** Lookup key in the static extensions: map of the UI build. */
1125
+ type ComponentKey = string;
1126
+ interface AdminManifest {
1127
+ schemaVersion: 1;
1128
+ project: {
1129
+ key: string;
1130
+ displayName: string;
1131
+ /** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
1132
+ label?: string;
1133
+ /** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
1134
+ icon?: string;
1135
+ logoUrl?: string;
1136
+ environment?: 'production' | 'staging' | 'development';
1137
+ /**
1138
+ * Allowed locale pool from the app config (`saas.yaml`
1139
+ * `marketing.availableLocales`). First = default..
1140
+ */
1141
+ availableLocales?: string[];
1142
+ /** Default locale; equals `availableLocales[0]`. */
1143
+ defaultLocale?: string;
1144
+ };
1145
+ build: {
1146
+ platformPackageVersion: string;
1147
+ appVersion: string;
1148
+ manifestHash: string;
1149
+ };
1150
+ planCatalogSnapshot: {
1151
+ source: string;
1152
+ hash: string;
1153
+ currency: string;
1154
+ vatRate: number;
1155
+ features?: FeatureDef[];
1156
+ plans: PlanDef[];
1157
+ };
1158
+ /** Map CapabilityKey → boolean. Manifest is never a security source. */
1159
+ capabilities: Record<CapabilityKey, boolean>;
1160
+ navigation: {
1161
+ standardPages: Partial<Record<StandardPageKey, StandardPageDef>>;
1162
+ projectPages?: ProjectPageDef[];
1163
+ };
1164
+ dashboard?: {
1165
+ kpiCards?: KpiCardDef[];
1166
+ };
1167
+ tenants?: {
1168
+ columns?: TenantColumnDef[];
1169
+ actions?: TenantActionDef[];
1170
+ };
1171
+ audit?: {
1172
+ actions?: AuditActionDef[];
1173
+ };
1174
+ }
1175
+ type StandardPageKey = 'dashboard' | 'tenants' | 'subscriptions' | 'promoCodes' | 'plans' | 'audit' | 'users' | 'pilots' | 'discovery' | 'bundles' | 'marketingCatalog' | 'platformEmail' | 'platformEmailHistory';
1176
+ interface StandardPageDef {
1177
+ enabled: boolean;
1178
+ requiredCapability?: CapabilityKey;
1179
+ }
1180
+ interface ProjectPageDef {
1181
+ /** `<app>.<area>`, e.g. `demoapp.datev`. */
1182
+ id: string;
1183
+ label: string;
1184
+ icon?: string;
1185
+ /** Frontend route, e.g. `/admin/datev`. */
1186
+ route: string;
1187
+ navSection?: string;
1188
+ /** Lookup in the static extensions: map of the shell build. */
1189
+ componentKey: ComponentKey;
1190
+ requiredCapability?: CapabilityKey;
1191
+ prefetchOnIdle?: boolean;
1192
+ }
1193
+ interface KpiCardDef {
1194
+ id: string;
1195
+ label: string;
1196
+ /** Required path: /api/v1/admin/(extras|dashboard)/... */
1197
+ endpoint: string;
1198
+ displayHint: KpiDisplayHint;
1199
+ /** 0–100; UI sorts descending. */
1200
+ slotPriority?: number;
1201
+ requiredCapability?: CapabilityKey;
1202
+ }
1203
+ interface KpiDisplayHint {
1204
+ type: 'value' | 'value+timestamp' | 'value+spark8w' | 'value+delta';
1205
+ icon?: string;
1206
+ }
1207
+ interface TenantColumnDef {
1208
+ key: string;
1209
+ label: string;
1210
+ /** Required path: /api/v1/admin/extras/...; MUST be batch-capable, no {slug}/{tenantId}. */
1211
+ endpoint: string;
1212
+ requiredCapability?: CapabilityKey;
1213
+ }
1214
+ interface TenantActionDef {
1215
+ /** `<app>.<area>.<verb>`, e.g. `demoapp.datev.runExport`. */
1216
+ id: string;
1217
+ label: string;
1218
+ /** Lookup in the static actions: map of the shell build. */
1219
+ actionKey: ActionKey;
1220
+ requiredCapability?: CapabilityKey;
1221
+ requiresMfa?: boolean;
1222
+ confirmType?: 'none' | 'simple' | 'typed-slug' | 'typed-production' | 'date';
1118
1223
  }
1119
- /**
1120
- * Input for `publishBundleVersion()`. `nonRegressive` and
1121
- * `publishedChanges` are computed by the service from the diff against the
1122
- * predecessor version; the caller supplies only confirmation
1123
- * + user tag + validity dates.
1124
- *
1125
- * `validFrom` is **required** at publish (analogous to `PublishPlanVersionData`).
1126
- * If the draft already carries a `validFrom`, it is optional here. The
1127
- * service validates strictly > `validFrom` of the predecessor version and sets
1128
- * its `validUntil` via auto succession to `validFrom - 1 day`.
1129
- */
1130
- interface PublishBundleVersionData {
1131
- publishedByUserId: string | null;
1132
- /**
1133
- * If true and the diff classifies the version as regressive,
1134
- * it is published anyway — relevant for bulk-publish MFA confirmation
1135
- * ( editor-UI obligations).
1136
- */
1137
- forceRegressive?: boolean;
1138
- /**
1139
- * Allows publish despite an explicit price of 0.00. Default false: an
1140
- * explicit 0.00 publish is blocked (protection against seed placeholders).
1141
- * `null` prices (override resolution) are not affected.
1142
- */
1143
- allowZeroPrice?: boolean;
1144
- /**
1145
- * Required at publish if the draft has no `validFrom`. Must lie
1146
- * strictly after `validFrom` of the predecessor version. ISO-8601
1147
- * (`YYYY-MM-DD` or full timestamp).
1148
- */
1149
- validFrom?: string | null;
1150
- /**
1151
- * Optional; null = valid indefinitely (fits the last version
1152
- * of a bundle). When a successor version is created it is automatically
1153
- * overwritten by the service.
1154
- */
1155
- validUntil?: string | null;
1224
+ interface AuditActionDef {
1225
+ /** SCREAMING_SNAKE_CASE; matched to the AuditLog.action column. */
1226
+ key: string;
1227
+ label: string;
1228
+ severity?: 'info' | 'low' | 'medium' | 'high';
1156
1229
  }
1157
- /**
1158
- * Code of a strict-mode violation. lists the eight rules
1159
- * that are checked; each rule has its own code so the UI can show
1160
- * focused help texts.
1161
- */
1162
- type StrictModeWarningCode = 'CAPABILITY_MISSING' | 'CAPABILITY_RETIRED' | 'FEATURE_MISSING' | 'FEATURE_PLANNED_ONLY' | 'BUNDLE_FEATURE_UNKNOWN' | 'BUNDLE_PLAN_KEY_UNKNOWN' | 'PLAN_FEATURE_UNKNOWN' | 'PLAN_FEATURE_NOT_APPROVED' | 'BUNDLE_FEATURE_NOT_APPROVED' | 'PLAN_FEATURE_DEPENDENCY_UNSATISFIED' | 'BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED' | 'QUOTA_MISSING' | 'QUOTA_NOT_APPROVED' | 'VERSION_PUBLISH_OVERLAP';
1163
- /**
1164
- * A strict-mode violation. `field` points to the violating field
1165
- * (e.g. `'features[3]'`), `value` is the concrete value (e.g. `'INVENTORY'`).
1166
- */
1167
- interface StrictModeWarning {
1168
- code: StrictModeWarningCode;
1169
- /** Human-readable reason (German). */
1170
- message: string;
1171
- /** Path to the violating field; optional. */
1172
- field?: string;
1173
- /** The concrete violating value; optional. */
1174
- value?: string;
1230
+ interface ManifestContribution {
1231
+ capabilities?: Record<CapabilityKey, boolean>;
1232
+ navigation?: {
1233
+ standardPages?: Partial<Record<StandardPageKey, StandardPageDef>>;
1234
+ projectPages?: ProjectPageDef[];
1235
+ };
1236
+ dashboard?: {
1237
+ kpiCards?: KpiCardDef[];
1238
+ };
1239
+ tenants?: {
1240
+ columns?: TenantColumnDef[];
1241
+ actions?: TenantActionDef[];
1242
+ };
1243
+ audit?: {
1244
+ actions?: AuditActionDef[];
1245
+ };
1175
1246
  }
1176
- /**
1177
- * Service result for mutating Bundle operations
1178
- * (createDraft, updateDraft, publish): returns the persisted row plus
1179
- * a list of strict-mode warnings. In `warn-only` mode the
1180
- * warnings go into the UI as a banner; in `blocking` mode the service throws
1181
- * HTTP 422 instead, with the same warning list as the body.
1182
- */
1183
- interface BundleVersionMutationResult {
1184
- bundleVersion: BundleVersionRow;
1185
- warnings: StrictModeWarning[];
1247
+ interface PublicBootResponse {
1248
+ project: {
1249
+ key: string;
1250
+ displayName: string;
1251
+ /** Tag/subtitle (e.g. "SuperAdmin"). From `saas.yaml#app.label`. */
1252
+ label?: string;
1253
+ /** Short abbreviation for the logo badge (e.g. "ma", "da"). From `saas.yaml#app.icon`. */
1254
+ icon?: string;
1255
+ logoUrl?: string;
1256
+ environment?: 'production' | 'staging' | 'development';
1257
+ };
1258
+ }
1259
+
1260
+ /** Format: 'web:<email>:<sessionId>' or 'cli:<email>:<host>'. */
1261
+ type ActorTag = string;
1262
+ interface AuditEntry {
1263
+ id: string;
1264
+ /** null = platform action without tenant context (SUPER_ADMIN). */
1265
+ tenantId: string | null;
1266
+ /** null = system / cron-triggered. */
1267
+ userId: string | null;
1268
+ /** Convenience field; backend resolves it from userId. */
1269
+ userEmail: string | null;
1270
+ /** e.g. 'Tenant', 'PromoCode', 'Subscription', 'PlanVersion', 'User'. */
1271
+ entity: string;
1272
+ entityId: string;
1273
+ /** SCREAMING_SNAKE_CASE; past-tense oriented. */
1274
+ action: string;
1275
+ /** Freely structured. Convention: { field: { old, new } } or { reason, ... }. */
1276
+ changes: Record<string, unknown> | null;
1277
+ actorTag: ActorTag | null;
1278
+ ipAddress: string | null;
1279
+ userAgent: string | null;
1280
+ createdAt: string;
1281
+ }
1282
+ interface AuditQuery {
1283
+ tenantId?: string;
1284
+ userId?: string;
1285
+ entity?: string;
1286
+ entityId?: string;
1287
+ action?: string;
1288
+ /** Wildcard-capable, e.g. 'cli:*'. */
1289
+ actorTag?: string;
1290
+ from?: string;
1291
+ to?: string;
1292
+ page?: number;
1293
+ pageSize?: number;
1186
1294
  }
1187
1295
 
1188
1296
  /** Promotion type. */
@@ -1213,7 +1321,6 @@ type PromotionI18n = Record<string, PromotionI18nFields>;
1213
1321
  /** Wire format of a `promotions` row. */
1214
1322
  interface PromotionRow {
1215
1323
  id: string;
1216
- projectKey: string;
1217
1324
  /** Internal label (not public). */
1218
1325
  internalLabel: string;
1219
1326
  type: PromotionType;
@@ -1239,11 +1346,7 @@ interface PromotionRow {
1239
1346
  createdAt: string;
1240
1347
  updatedAt: string;
1241
1348
  }
1242
- interface PromotionFilter {
1243
- projectKey: string;
1244
- }
1245
1349
  interface CreatePromotionData {
1246
- projectKey: string;
1247
1350
  internalLabel: string;
1248
1351
  type: PromotionType;
1249
1352
  value: PromotionValue;
@@ -1366,7 +1469,6 @@ type CheckoutOfferStatus = 'open' | 'consumed' | 'expired';
1366
1469
  /** Wire format of a `checkout_offers` row. */
1367
1470
  interface CheckoutOfferRow {
1368
1471
  id: string;
1369
- projectKey: string;
1370
1472
  /** Plan selected on the website. */
1371
1473
  planKey: string;
1372
1474
  /** Resolved plan version, if known. */
@@ -1397,12 +1499,10 @@ interface CheckoutOfferRow {
1397
1499
  updatedAt: string;
1398
1500
  }
1399
1501
  interface CheckoutOfferFilter {
1400
- projectKey: string;
1401
1502
  status?: CheckoutOfferStatus;
1402
1503
  }
1403
1504
  /** Body of `POST /public/checkout-offer` — called from the website. */
1404
1505
  interface CreateCheckoutOfferData {
1405
- projectKey: string;
1406
1506
  planKey: string;
1407
1507
  planVersionId?: string | null;
1408
1508
  billingCycle: 'monthly' | 'yearly';
@@ -1438,7 +1538,6 @@ interface UpdateCheckoutOfferData {
1438
1538
 
1439
1539
  /** Wire format of the `marketing_settings` row. */
1440
1540
  interface MarketingSettingsRow {
1441
- projectKey: string;
1442
1541
  /** Runtime-activated subset of the `availableLocales` pool. */
1443
1542
  activeLocales: string[];
1444
1543
  updatedAt: string;
@@ -1556,7 +1655,6 @@ interface PublicComparisonRow {
1556
1655
  }
1557
1656
  /** Response of `GET /public/marketing-catalog`. */
1558
1657
  interface PublicMarketingCatalogResponse {
1559
- projectKey: string;
1560
1658
  locale: string;
1561
1659
  currency: string;
1562
1660
  /** VAT rate in percent — for the CheckoutOffer price breakdown. */
@@ -1666,7 +1764,7 @@ interface DiscoverySnapshot {
1666
1764
  /** ISO timestamp of the boot-time scan. */
1667
1765
  scannedAt: string;
1668
1766
  app: {
1669
- /** projectKey, same concept as in the catalog tables. */
1767
+ /** The application's name, from `saas.yaml#app.name`. */
1670
1768
  key: string;
1671
1769
  /** Backend version, e.g. from package.json. */
1672
1770
  version: string;
@@ -1838,7 +1936,6 @@ interface OnboardingPromoRedemption {
1838
1936
 
1839
1937
  interface PlanRow {
1840
1938
  id: string;
1841
- projectKey: string;
1842
1939
  planKey: string;
1843
1940
  label: string;
1844
1941
  description: string | null;
@@ -1855,7 +1952,6 @@ interface PlanRow {
1855
1952
  * creation (follows in M6 Pack 2).
1856
1953
  */
1857
1954
  interface CreatePlanData {
1858
- projectKey: string;
1859
1955
  planKey: string;
1860
1956
  label: string;
1861
1957
  description?: string | null;
@@ -1863,9 +1959,9 @@ interface CreatePlanData {
1863
1959
  sortOrder?: number;
1864
1960
  }
1865
1961
  /**
1866
- * Fields that may be changed on the plan stem. `planKey` and `projectKey`
1867
- * are deliberately not here — stem identity is immutable; whoever wants to
1868
- * change it creates a new plan and retires the old one.
1962
+ * Fields that may be changed on the plan stem. `planKey` is deliberately not
1963
+ * here — stem identity is immutable; whoever wants to change it creates a new
1964
+ * plan and retires the old one.
1869
1965
  */
1870
1966
  interface UpdatePlanData {
1871
1967
  label?: string;
@@ -1929,7 +2025,6 @@ interface UpsertResult {
1929
2025
  skipReason?: string;
1930
2026
  }
1931
2027
  interface UpsertPlanInput {
1932
- projectKey: string;
1933
2028
  planKey: string;
1934
2029
  label: string;
1935
2030
  description?: string | null;
@@ -1949,7 +2044,6 @@ interface UpsertPlanVersionInput {
1949
2044
  changeNote: string;
1950
2045
  }
1951
2046
  interface UpsertFeatureCatalogEntryInput {
1952
- projectKey: string;
1953
2047
  featureKey: FeatureKey;
1954
2048
  label?: string;
1955
2049
  icon?: string;
@@ -1995,7 +2089,7 @@ interface PlanCatalogReadSnapshot {
1995
2089
  * implement it against their Prisma tables.
1996
2090
  */
1997
2091
  interface PlanCatalogReadSink {
1998
- loadSnapshot(projectKey: string): Promise<PlanCatalogReadSnapshot>;
2092
+ loadSnapshot(): Promise<PlanCatalogReadSnapshot>;
1999
2093
  }
2000
2094
 
2001
2095
  /**
@@ -2439,6 +2533,26 @@ interface PromoRevenueDeductionAggregator {
2439
2533
 
2440
2534
  type ContractLineItemKind = 'plan' | 'bundle' | 'discount';
2441
2535
  type SubscriptionContractStatus = 'active' | 'scheduled' | 'terminated' | 'superseded';
2536
+ /**
2537
+ * The statuses a contract is looked up under when asking "what is this tenant
2538
+ * on right now" — `scheduled` included, because a contract that starts today
2539
+ * and has not been switched to `active` yet is still the one in force at its
2540
+ * own `effectiveFrom`.
2541
+ *
2542
+ * One list rather than one per adapter: the two adapters have to answer
2543
+ * `findActiveByTenantId` the same way, and a status added here must not reach
2544
+ * only whichever of them somebody remembered.
2545
+ */
2546
+ /**
2547
+ * How many bundle versions one price lookup may name.
2548
+ *
2549
+ * One number rather than two: the server validates against it and the client
2550
+ * batches to stay inside it, and a client that learned the cap by receiving a
2551
+ * 400 would fail silently — the lookup answers with an empty map, and every
2552
+ * card falls back to a catalogue price the tenant may not be charged.
2553
+ */
2554
+ declare const BUNDLE_PRICE_LOOKUP_LIMIT = 200;
2555
+ declare const ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES: readonly SubscriptionContractStatus[];
2442
2556
  interface ContractLineItemRecord {
2443
2557
  id: string;
2444
2558
  contractId: string;
@@ -2469,7 +2583,6 @@ interface SubscriptionContractPriceSnapshot {
2469
2583
  }
2470
2584
  interface SubscriptionContractRecord {
2471
2585
  id: string;
2472
- projectKey: string;
2473
2586
  tenantId: string;
2474
2587
  status: SubscriptionContractStatus;
2475
2588
  effectiveFrom: Date;
@@ -2488,7 +2601,6 @@ interface SubscriptionContractRecord {
2488
2601
  }
2489
2602
  type NewContractLineItemData = Omit<ContractLineItemRecord, 'id' | 'contractId' | 'createdAt'>;
2490
2603
  interface CreateSubscriptionContractData {
2491
- projectKey: string;
2492
2604
  tenantId: string;
2493
2605
  status?: SubscriptionContractStatus;
2494
2606
  effectiveFrom: Date;
@@ -2521,7 +2633,6 @@ interface TerminateSubscriptionContractData {
2521
2633
  status: Extract<SubscriptionContractStatus, 'terminated' | 'superseded'> | null;
2522
2634
  }
2523
2635
  interface SubscriptionContractFilter {
2524
- projectKey?: string;
2525
2636
  tenantId?: string;
2526
2637
  status?: SubscriptionContractStatus;
2527
2638
  asOf?: Date;
@@ -2543,7 +2654,6 @@ interface InvoiceLineItemSnapshot {
2543
2654
  }
2544
2655
  interface SubscriptionContractInvoiceSnapshot {
2545
2656
  contractId: string;
2546
- projectKey: string;
2547
2657
  tenantId: string;
2548
2658
  originalOfferId: string | null;
2549
2659
  currency: string;
@@ -2651,11 +2761,10 @@ interface SubscriptionRepository {
2651
2761
  countByBundleVersionId?(bundleVersionId: string): Promise<number>;
2652
2762
  /**
2653
2763
  * Counts active subscriptions (status `ACTIVE` or `TRIAL`) per plan key,
2654
- * platform-wide across all tenants of the project — feeds the tenant
2655
- * column of the SuperAdmin plan list (`GET /admin/catalog/plans/tenant-counts`).
2764
+ * platform-wide across every tenant — feeds the tenant column of the
2765
+ * SuperAdmin plan list (`GET /admin/catalog/plans/tenant-counts`).
2656
2766
  * Cross-version: counts the plan, not a single PlanVersion
2657
- * (subscriptions on superseded versions are included). `projectKey` is
2658
- * informational for single-project consumers.
2767
+ * (subscriptions on superseded versions are included).
2659
2768
  *
2660
2769
  * Returns a map `planKey → count`; plans without an active subscription
2661
2770
  * are missing (UI defaults to 0). Platform-wide count across all tenants →
@@ -2663,7 +2772,7 @@ interface SubscriptionRepository {
2663
2772
  *
2664
2773
  * Optional — if not implemented, the tenant column stays 0.
2665
2774
  */
2666
- countActiveByPlanKey?(projectKey: string): Promise<Record<string, number>>;
2775
+ countActiveByPlanKey?(): Promise<Record<string, number>>;
2667
2776
  }
2668
2777
  /**
2669
2778
  * Adapter for the `subscription_bundles` junction.
@@ -2764,6 +2873,19 @@ interface SubscriptionUsageRecord {
2764
2873
  * existed — readers treat that as "the period end is the answer".
2765
2874
  */
2766
2875
  minimumTermUntil?: Date | null;
2876
+ /**
2877
+ * The day of the month the subscription is billed on, 1–31.
2878
+ *
2879
+ * Read by the cancellation rules: a declaration after the notice window
2880
+ * lands one period past the term end, and that step has to measure from the
2881
+ * billing day rather than from a term end that may already have been
2882
+ * clamped by a short month.
2883
+ *
2884
+ * Optional, because an adapter that does not store the column keeps today's
2885
+ * behaviour — the step then takes its day from the term end, which is
2886
+ * correct except in the month after a clamp.
2887
+ */
2888
+ billingAnchorDay?: number | null;
2767
2889
  /**
2768
2890
  * When a cancellation was declared, and when it lands.
2769
2891
  *
@@ -2927,6 +3049,34 @@ type RedeemPromoInTransactionCallback = (tx: TransactionContext, subscriptionId:
2927
3049
  * app-specific. The platform service calls `invalidateTenant` in the
2928
3050
  * EntitlementService after a successful adapter call.
2929
3051
  */
3052
+ /** What `cancelSubscription` is told to write. Named so both adapters spell
3053
+ * the same shape once rather than each restating it. */
3054
+ interface CancelSubscriptionInput {
3055
+ canceledAt: Date;
3056
+ effectiveAt: Date;
3057
+ terminateNow: boolean;
3058
+ minimumTermUntil?: Date;
3059
+ }
3060
+ /** What `cancelSubscription` answers with. */
3061
+ interface CancelSubscriptionResult {
3062
+ canceledAt: Date | null;
3063
+ canceledEffectiveAt: Date | null;
3064
+ status: string;
3065
+ /**
3066
+ * True when a cancellation was already recorded and this call changed
3067
+ * nothing — the stored dates are returned instead.
3068
+ *
3069
+ * The caller checks first, but a check and a write are two moments, and two
3070
+ * requests can pass the check before either writes. Straddling a notice
3071
+ * deadline that costs a billing cycle: the first declaration lands on time,
3072
+ * the second recomputes against a later `now`, and an unconditional write
3073
+ * replaces the first answer with one a period further out. An
3074
+ * implementation therefore claims the row only while both cancellation
3075
+ * fields are still empty, and answers `true` here when the claim finds
3076
+ * nothing to claim.
3077
+ */
3078
+ alreadyCanceled: boolean;
3079
+ }
2930
3080
  interface TenantSubscriptionWritePort {
2931
3081
  /** Immediate change: set plan + cycle, clear pending fields, optionally reset the period. */
2932
3082
  changePlanImmediate(tenantId: string, input: ImmediatePlanChangeInput): Promise<{
@@ -2974,30 +3124,7 @@ interface TenantSubscriptionWritePort {
2974
3124
  * deadline buys the following period. Left unset the stored term end is
2975
3125
  * unchanged, which is the ordinary case.
2976
3126
  */
2977
- cancelSubscription(tenantId: string, input: {
2978
- canceledAt: Date;
2979
- effectiveAt: Date;
2980
- terminateNow: boolean;
2981
- minimumTermUntil?: Date;
2982
- }): Promise<{
2983
- canceledAt: Date | null;
2984
- canceledEffectiveAt: Date | null;
2985
- status: string;
2986
- /**
2987
- * True when a cancellation was already recorded and this call changed
2988
- * nothing — the stored dates are returned instead.
2989
- *
2990
- * The caller checks first, but a check and a write are two moments, and
2991
- * two requests can pass the check before either writes. Straddling a
2992
- * notice deadline that costs a billing cycle: the first declaration
2993
- * lands on time, the second recomputes against a later `now`, and an
2994
- * unconditional write replaces the first answer with one a period
2995
- * further out. An implementation therefore claims the row only while
2996
- * both cancellation fields are still empty, and answers `true` here
2997
- * when the claim finds nothing to claim.
2998
- */
2999
- alreadyCanceled: boolean;
3000
- }>;
3127
+ cancelSubscription(tenantId: string, input: CancelSubscriptionInput): Promise<CancelSubscriptionResult>;
3001
3128
  /**
3002
3129
  * Atomic onboarding creation: sets plan + cycle + period window
3003
3130
  * AND optionally calls a promo-redeem callback — all in a
@@ -3249,7 +3376,6 @@ interface RlsBypassPort {
3249
3376
 
3250
3377
  /** Filter for `PlanRepository.list()`. */
3251
3378
  interface PlanListFilter {
3252
- projectKey: string;
3253
3379
  /** Exclude soft-deleted plans — default `true`. */
3254
3380
  excludeDeleted?: boolean;
3255
3381
  /**
@@ -3283,7 +3409,7 @@ interface PlanListFilter {
3283
3409
  interface PlanRepository {
3284
3410
  list(filter: PlanListFilter): Promise<PlanRow[]>;
3285
3411
  findById(planId: string): Promise<PlanRow | null>;
3286
- findByKey(projectKey: string, planKey: string): Promise<PlanRow | null>;
3412
+ findByKey(planKey: string): Promise<PlanRow | null>;
3287
3413
  create(data: CreatePlanData): Promise<PlanRow>;
3288
3414
  update(planId: string, data: UpdatePlanData): Promise<PlanRow>;
3289
3415
  /** Sets `deletedAt` to NOW(); soft-deleted plans are filtered from `list` by default. */
@@ -3384,10 +3510,24 @@ interface PlanRepository {
3384
3510
  }
3385
3511
  /** Filter for `BundleRepository.list()`. */
3386
3512
  interface BundleListFilter {
3387
- projectKey: string;
3388
3513
  /** Exclude soft-deleted bundles — default `true`. */
3389
3514
  excludeDeleted?: boolean;
3390
3515
  }
3516
+ /**
3517
+ * What publishing a bundle draft records.
3518
+ *
3519
+ * Named because it was written out three times — the port, and each adapter's
3520
+ * implementation of it — and a signature restated is a contract restated: the
3521
+ * copies can drift, and nothing but a reader would notice.
3522
+ */
3523
+ interface PublishBundleVersionMeta {
3524
+ publishedByUserId: string | null;
3525
+ publishedChanges: VersionChange[];
3526
+ nonRegressive: boolean;
3527
+ /** Required — validated by the service before the repository call. */
3528
+ validFrom: Date;
3529
+ validUntil: Date | null;
3530
+ }
3391
3531
  /**
3392
3532
  * Adapter for `Bundle` + `BundleVersion` persistence. Consumers implement
3393
3533
  * this against their Prisma tables (`bundles` + `bundle_versions`).
@@ -3406,7 +3546,7 @@ interface BundleListFilter {
3406
3546
  interface BundleRepository {
3407
3547
  list(filter: BundleListFilter): Promise<BundleRow[]>;
3408
3548
  findById(bundleId: string): Promise<BundleRow | null>;
3409
- findByKey(projectKey: string, bundleKey: string): Promise<BundleRow | null>;
3549
+ findByKey(bundleKey: string): Promise<BundleRow | null>;
3410
3550
  create(data: CreateBundleData): Promise<BundleRow>;
3411
3551
  update(bundleId: string, data: UpdateBundleData): Promise<BundleRow>;
3412
3552
  /** Sets `deletedAt` to NOW(); soft-deleted bundles are filtered from `list` by default. */
@@ -3465,14 +3605,7 @@ interface BundleRepository {
3465
3605
  * if the predecessor carries a `validUntil` — the adapter only
3466
3606
  * persists, it does not validate again.
3467
3607
  */
3468
- publishDraft(versionId: string, publishMeta: {
3469
- publishedByUserId: string | null;
3470
- publishedChanges: VersionChange[];
3471
- nonRegressive: boolean;
3472
- /** Required — validated by the service before the repository call. */
3473
- validFrom: Date;
3474
- validUntil: Date | null;
3475
- }, tx?: TransactionContext): Promise<BundleVersionRow>;
3608
+ publishDraft(versionId: string, publishMeta: PublishBundleVersionMeta, tx?: TransactionContext): Promise<BundleVersionRow>;
3476
3609
  /**
3477
3610
  * Hard-discards a draft version (`publishedAt === null`) from the DB.
3478
3611
  * Throws if the version was already published — published versions
@@ -3510,7 +3643,6 @@ interface MarketingProjectionRepository {
3510
3643
  }
3511
3644
  /** Upsert input for a capability from the discovery sync. */
3512
3645
  interface UpsertCapabilityEntryData {
3513
- projectKey: string;
3514
3646
  capabilityKey: string;
3515
3647
  label: string;
3516
3648
  description: string | null;
@@ -3527,7 +3659,6 @@ interface UpsertCapabilityEntryData {
3527
3659
  }
3528
3660
  /** Upsert input for a feature from the discovery sync. */
3529
3661
  interface UpsertFeatureEntryData {
3530
- projectKey: string;
3531
3662
  featureKey: string;
3532
3663
  label: string;
3533
3664
  description: string | null;
@@ -3541,7 +3672,6 @@ interface UpsertFeatureEntryData {
3541
3672
  }
3542
3673
  /** Upsert input for a quota from the discovery sync. */
3543
3674
  interface UpsertQuotaEntryData {
3544
- projectKey: string;
3545
3675
  quotaKey: string;
3546
3676
  label: string;
3547
3677
  description: string | null;
@@ -3571,7 +3701,7 @@ interface SetCatalogEntryReviewData {
3571
3701
  * Prisma tables.
3572
3702
  *
3573
3703
  * Binding:
3574
- * - `upsert*` matches on (`projectKey`, `<key>`) and leaves `i18n`,
3704
+ * - `upsert*` matches on `<key>` and leaves `i18n`,
3575
3705
  * `sortOrder`, `createdAt` as well as the approval fields (`approvedAt`/
3576
3706
  * `approvedBy`/`approvedSignature`) **untouched** on an update —
3577
3707
  * only the code-derived fields + the status (resolved by the service)
@@ -3587,7 +3717,7 @@ interface CatalogEntryRepository {
3587
3717
  upsertCapability(data: UpsertCapabilityEntryData): Promise<CapabilityCatalogEntryRow>;
3588
3718
  upsertFeature(data: UpsertFeatureEntryData): Promise<FeatureCatalogEntryRow>;
3589
3719
  upsertQuota(data: UpsertQuotaEntryData): Promise<QuotaCatalogEntryRow>;
3590
- retireMissing(projectKey: string, type: 'capability' | 'feature' | 'quota', presentKeys: string[]): Promise<number>;
3720
+ retireMissing(type: 'capability' | 'feature' | 'quota', presentKeys: string[]): Promise<number>;
3591
3721
  /**
3592
3722
  * Sets or clears the successor pointer of a feature/quota
3593
3723
  * (#39). The sync calls this when a key disappears from the snapshot
@@ -3596,17 +3726,17 @@ interface CatalogEntryRepository {
3596
3726
  * adapters without a `successor_key` column omit the methods, and the sync
3597
3727
  * then skips the pointers with a warn log.
3598
3728
  */
3599
- setFeatureSuccessor?(projectKey: string, featureKey: string, successorKey: string | null): Promise<FeatureCatalogEntryRow>;
3600
- setQuotaSuccessor?(projectKey: string, quotaKey: string, successorKey: string | null): Promise<QuotaCatalogEntryRow>;
3601
- findFeature(projectKey: string, featureKey: string): Promise<FeatureCatalogEntryRow | null>;
3602
- findQuota(projectKey: string, quotaKey: string): Promise<QuotaCatalogEntryRow | null>;
3603
- setFeatureReview(projectKey: string, featureKey: string, data: SetCatalogEntryReviewData): Promise<FeatureCatalogEntryRow>;
3604
- setQuotaReview(projectKey: string, quotaKey: string, data: SetCatalogEntryReviewData): Promise<QuotaCatalogEntryRow>;
3605
- setFeatureI18n(projectKey: string, featureKey: string, i18n: CatalogEntryI18n): Promise<FeatureCatalogEntryRow>;
3606
- setQuotaI18n(projectKey: string, quotaKey: string, i18n: CatalogEntryI18n): Promise<QuotaCatalogEntryRow>;
3729
+ setFeatureSuccessor?(featureKey: string, successorKey: string | null): Promise<FeatureCatalogEntryRow>;
3730
+ setQuotaSuccessor?(quotaKey: string, successorKey: string | null): Promise<QuotaCatalogEntryRow>;
3731
+ findFeature(featureKey: string): Promise<FeatureCatalogEntryRow | null>;
3732
+ findQuota(quotaKey: string): Promise<QuotaCatalogEntryRow | null>;
3733
+ setFeatureReview(featureKey: string, data: SetCatalogEntryReviewData): Promise<FeatureCatalogEntryRow>;
3734
+ setQuotaReview(quotaKey: string, data: SetCatalogEntryReviewData): Promise<QuotaCatalogEntryRow>;
3735
+ setFeatureI18n(featureKey: string, i18n: CatalogEntryI18n): Promise<FeatureCatalogEntryRow>;
3736
+ setQuotaI18n(quotaKey: string, i18n: CatalogEntryI18n): Promise<QuotaCatalogEntryRow>;
3607
3737
  /** Sets the editable base fields (default locale `label`/`description`). */
3608
- setFeatureBase(projectKey: string, featureKey: string, data: UpdateCatalogEntryBaseData): Promise<FeatureCatalogEntryRow>;
3609
- setQuotaBase(projectKey: string, quotaKey: string, data: UpdateCatalogEntryBaseData): Promise<QuotaCatalogEntryRow>;
3738
+ setFeatureBase(featureKey: string, data: UpdateCatalogEntryBaseData): Promise<FeatureCatalogEntryRow>;
3739
+ setQuotaBase(quotaKey: string, data: UpdateCatalogEntryBaseData): Promise<QuotaCatalogEntryRow>;
3610
3740
  }
3611
3741
  /**
3612
3742
  * Adapter for `promotions`. **No versioning** — promotions are edited
@@ -3614,7 +3744,7 @@ interface CatalogEntryRepository {
3614
3744
  * this against their `promotions` Prisma table.
3615
3745
  */
3616
3746
  interface PromotionRepository {
3617
- list(filter: PromotionFilter): Promise<PromotionRow[]>;
3747
+ list(): Promise<PromotionRow[]>;
3618
3748
  findById(id: string): Promise<PromotionRow | null>;
3619
3749
  create(data: CreatePromotionData): Promise<PromotionRow>;
3620
3750
  update(id: string, data: UpdatePromotionData): Promise<PromotionRow>;
@@ -3622,13 +3752,14 @@ interface PromotionRepository {
3622
3752
  delete(id: string): Promise<void>;
3623
3753
  }
3624
3754
  /**
3625
- * Adapter for `marketing_settings` — one row per project. `get` returns
3626
- * `null` as long as the SuperAdmin has saved nothing (then the full
3627
- * `availableLocales` pool counts as active). `upsert` creates the row or replaces it.
3755
+ * Adapter for `marketing_settings` — at most one row, which a `CHECK` on the
3756
+ * canonical schema holds rather than convention. `get` returns `null` as long
3757
+ * as the SuperAdmin has saved nothing (then the full `availableLocales` pool
3758
+ * counts as active). `upsert` creates the row or replaces it.
3628
3759
  */
3629
3760
  interface MarketingSettingsRepository {
3630
- get(projectKey: string): Promise<MarketingSettingsRow | null>;
3631
- upsert(projectKey: string, data: UpdateMarketingSettingsData): Promise<MarketingSettingsRow>;
3761
+ get(): Promise<MarketingSettingsRow | null>;
3762
+ upsert(data: UpdateMarketingSettingsData): Promise<MarketingSettingsRow>;
3632
3763
  }
3633
3764
 
3634
3765
  /**
@@ -3840,6 +3971,8 @@ declare const CATALOG_ERROR_CODES: {
3840
3971
  readonly BUNDLE_VERSION_SUPERSEDED: "BUNDLE_VERSION_SUPERSEDED";
3841
3972
  readonly BUNDLE_VERSION_REGRESSION: "BUNDLE_VERSION_REGRESSION";
3842
3973
  readonly BUNDLE_VERSION_ZERO_PRICE: "BUNDLE_VERSION_ZERO_PRICE";
3974
+ readonly BUNDLE_VERSION_NO_PRICE: "BUNDLE_VERSION_NO_PRICE";
3975
+ readonly BUNDLE_VERSION_NOT_PRICED_FOR_PLAN: "BUNDLE_VERSION_NOT_PRICED_FOR_PLAN";
3843
3976
  readonly BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED";
3844
3977
  readonly BUNDLE_VERSION_VALID_FROM_REQUIRED: "BUNDLE_VERSION_VALID_FROM_REQUIRED";
3845
3978
  readonly BUNDLE_VERSION_VALID_FROM_INVALID: "BUNDLE_VERSION_VALID_FROM_INVALID";
@@ -3884,6 +4017,8 @@ declare const BILLING_ERROR_CODES: {
3884
4017
  readonly BUNDLE_ALREADY_SUBSCRIBED: "BUNDLE_ALREADY_SUBSCRIBED";
3885
4018
  readonly BUNDLE_INCOMPATIBLE_WITH_PLAN: "BUNDLE_INCOMPATIBLE_WITH_PLAN";
3886
4019
  readonly BUNDLE_NOT_SELF_SERVICE: "BUNDLE_NOT_SELF_SERVICE";
4020
+ readonly BUNDLE_CYCLE_EXCEEDS_PLAN: "BUNDLE_CYCLE_EXCEEDS_PLAN";
4021
+ readonly BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "BUNDLE_NOT_PRICED_FOR_THIS_PLAN";
3887
4022
  readonly SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED";
3888
4023
  readonly SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SUBSCRIPTION_BUNDLE_NOT_CANCELLED";
3889
4024
  readonly SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE";
@@ -4040,6 +4175,8 @@ declare const PLATFORM_ERROR_CODES: {
4040
4175
  readonly BUNDLE_ALREADY_SUBSCRIBED: "BUNDLE_ALREADY_SUBSCRIBED";
4041
4176
  readonly BUNDLE_INCOMPATIBLE_WITH_PLAN: "BUNDLE_INCOMPATIBLE_WITH_PLAN";
4042
4177
  readonly BUNDLE_NOT_SELF_SERVICE: "BUNDLE_NOT_SELF_SERVICE";
4178
+ readonly BUNDLE_CYCLE_EXCEEDS_PLAN: "BUNDLE_CYCLE_EXCEEDS_PLAN";
4179
+ readonly BUNDLE_NOT_PRICED_FOR_THIS_PLAN: "BUNDLE_NOT_PRICED_FOR_THIS_PLAN";
4043
4180
  readonly SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED";
4044
4181
  readonly SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SUBSCRIPTION_BUNDLE_NOT_CANCELLED";
4045
4182
  readonly SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE";
@@ -4093,6 +4230,8 @@ declare const PLATFORM_ERROR_CODES: {
4093
4230
  readonly BUNDLE_VERSION_SUPERSEDED: "BUNDLE_VERSION_SUPERSEDED";
4094
4231
  readonly BUNDLE_VERSION_REGRESSION: "BUNDLE_VERSION_REGRESSION";
4095
4232
  readonly BUNDLE_VERSION_ZERO_PRICE: "BUNDLE_VERSION_ZERO_PRICE";
4233
+ readonly BUNDLE_VERSION_NO_PRICE: "BUNDLE_VERSION_NO_PRICE";
4234
+ readonly BUNDLE_VERSION_NOT_PRICED_FOR_PLAN: "BUNDLE_VERSION_NOT_PRICED_FOR_PLAN";
4096
4235
  readonly BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED";
4097
4236
  readonly BUNDLE_VERSION_VALID_FROM_REQUIRED: "BUNDLE_VERSION_VALID_FROM_REQUIRED";
4098
4237
  readonly BUNDLE_VERSION_VALID_FROM_INVALID: "BUNDLE_VERSION_VALID_FROM_INVALID";
@@ -4911,6 +5050,98 @@ interface SetupConfirmMfaResponse {
4911
5050
  ok: boolean;
4912
5051
  }
4913
5052
 
5053
+ /** What the adapter's schema can actually answer about a version's dates. */
5054
+ interface PlanVersionMappingFields {
5055
+ /** `validFrom`/`validUntil` are maintained; otherwise both read as null. */
5056
+ validityWindows: boolean;
5057
+ /** `endsAt` exists; otherwise the field is left off the record entirely. */
5058
+ endsAt: boolean;
5059
+ }
5060
+ /** A `plans` row as either adapter reads it back. */
5061
+ interface CanonicalPlanRow {
5062
+ id: string;
5063
+ planKey: string;
5064
+ label: string;
5065
+ description: string | null;
5066
+ icon: string | null;
5067
+ sortOrder: number;
5068
+ createdAt: Date;
5069
+ updatedAt: Date;
5070
+ deletedAt: Date | null;
5071
+ }
5072
+ /** A `plan_versions` row as either adapter reads it back. */
5073
+ interface CanonicalPlanVersionRow {
5074
+ id: string;
5075
+ version: number;
5076
+ baseVersionId: string | null;
5077
+ features: unknown;
5078
+ quotas: unknown;
5079
+ monthlyNet: unknown;
5080
+ yearlyNet: unknown;
5081
+ marketed: boolean;
5082
+ publishedAt: Date | null;
5083
+ supersededAt: Date | null;
5084
+ publishedChanges: unknown;
5085
+ changeNote: string;
5086
+ nonRegressive: boolean;
5087
+ validFrom?: Date | null;
5088
+ validUntil?: Date | null;
5089
+ endsAt?: Date | null;
5090
+ createdByUserId: string | null;
5091
+ publishedByUserId: string | null;
5092
+ createdAt: Date;
5093
+ updatedAt: Date;
5094
+ }
5095
+ declare function toPlanRow(row: CanonicalPlanRow): PlanRow;
5096
+ /**
5097
+ * `planKey` is passed rather than read off the row: the canonical schema stores
5098
+ * the plan key in `planId`, but an adapter translating a consumer schema with a
5099
+ * real foreign key has to resolve it first, and only the adapter knows which
5100
+ * shape it is looking at.
5101
+ */
5102
+ declare function toPlanVersionRow(row: CanonicalPlanVersionRow, planKey: string, fields: PlanVersionMappingFields): PlanVersionRow;
5103
+
5104
+ /** A `subscription_contracts` row as either adapter reads it back. */
5105
+ interface CanonicalContractRow {
5106
+ id: string;
5107
+ tenantId: string;
5108
+ status: string;
5109
+ effectiveFrom: Date;
5110
+ effectiveUntil: Date | null;
5111
+ originalOfferId: string | null;
5112
+ originalPlanVersionId: string | null;
5113
+ originalBundleVersionIds: unknown;
5114
+ entitlementSnapshot: unknown;
5115
+ priceSnapshot: unknown;
5116
+ promotionSnapshots: unknown;
5117
+ promoCodeSnapshots: unknown;
5118
+ termsSnapshot: unknown;
5119
+ createdAt: Date;
5120
+ updatedAt: Date;
5121
+ }
5122
+ /** A `contract_line_items` row as either adapter reads it back. */
5123
+ interface CanonicalContractLineItemRow {
5124
+ id: string;
5125
+ contractId: string;
5126
+ kind: string;
5127
+ sourceKey: string;
5128
+ sourceVersionId: string | null;
5129
+ titleSnapshot: string;
5130
+ descriptionSnapshot: string | null;
5131
+ quantity: number;
5132
+ unit: string | null;
5133
+ priceNet: unknown;
5134
+ priceGross: unknown;
5135
+ billingCycle: string;
5136
+ minimumTermUntil: Date | null;
5137
+ featuresSnapshot: unknown;
5138
+ quotaEffectsSnapshot: unknown;
5139
+ metadata: unknown;
5140
+ createdAt: Date;
5141
+ }
5142
+ declare function toSubscriptionContractRecord(row: CanonicalContractRow, lineItems: CanonicalContractLineItemRow[]): SubscriptionContractRecord;
5143
+ declare function toContractLineItemRecord(row: CanonicalContractLineItemRow): ContractLineItemRecord;
5144
+
4914
5145
  /** Why a version is editable (for UI badges + audit logs). */
4915
5146
  type VersionEditableReason = 'draft' | 'pre-active';
4916
5147
  interface VersionEditability {
@@ -4951,4 +5182,4 @@ declare function resolveErrorMessage(body: Partial<PlatformErrorBody> & Record<s
4951
5182
 
4952
5183
  declare const ERROR_MESSAGES_DE: Record<PlatformErrorCode, string>;
4953
5184
 
4954
- export { AUTH_ERROR_CODES, type ActionKey, type ActivationOrchestrator, type ActivePlanVersionWhere, type ActivePlanVersionWhereWithEndsAt, type ActiveVersionWhere, type ActiveVersionWhereWithEndsAt, type ActorTag, type AdminActor, type AdminAuditListFilter, type AdminManifest, type AdminResourcesPort, type AdminSubscriptionListRow, type AdminTenantDetail, type AdminTenantListFilter, type AdminTenantListRow, type AdminTenantStateResult, type AdminUserListFilter, type AdminUserListRow, type ApplyOnboardingSelectionInput, type ApplyOnboardingSelectionResult, type ApprovedCatalogKeys, type AuditActionDef, type AuditEntry, type AuditPort, type AuditQuery, type AuditQueryPort, type AuditStatsPort, type AuditStatsSnapshot, type AuthErrorCode, BILLING_ERROR_CODES, type BillingCycle, type BillingErrorCode, type BundleAvailabilityState, type BundleCompatibility, type BundleFeatureShape, type BundleListFilter, type BundlePricingOverride, type BundleRepository, type BundleRow, type BundleVersionFields, type BundleVersionMutationResult, type BundleVersionRow, CATALOG_ERROR_CODES, CONTRACT_ERROR_CODES, type CancelSubscriptionBundleData, type CapabilityCatalogEntryRow, type CapabilityCodeStatus, type CapabilityKey, type CapabilityKind, type CatalogEntryFilter, type CatalogEntryI18n, type CatalogEntryI18nFields, type CatalogEntryRepository, type CatalogErrorCode, type ChangeDirection, type CheckoutOfferFilter, type CheckoutOfferLineItem, type CheckoutOfferLineItemKind, type CheckoutOfferPriceBreakdown, type CheckoutOfferPromoCodeSnapshot, type CheckoutOfferPromotionSnapshot, type CheckoutOfferRepository, type CheckoutOfferRow, type CheckoutOfferStatus, type CheckoutSession, type CleanupResult, type CliUserRow, type ComponentKey, type ConfiguratorCatalog, type ConfiguratorMarketingProvider, type ConfiguratorModel, type ConfiguratorPlanMarketing, type ConfiguratorPlanVersionRow, type ConfiguratorPriceBreakdown, type ConfiguratorSourcesLookup, type ContractErrorCode, type ContractLineItemKind, type ContractLineItemRecord, type CreateBundleData, type CreateBundleVersionDraftData, type CreateCheckoutOfferData, type CreateMarketingProjectionData, type CreatePlanData, type CreatePlanVersionDraftData, type CreatePromoCodeData, type CreatePromoCodeRequest, type CreatePromotionData, type CreateSubscriptionBundleData, type CreateSubscriptionContractData, type CreateSuperAdminCliInput, type CreateTenantInput, type DiffResult, type DiscoveredCapability, type DiscoveredFeature, type DiscoveredQuota, type DiscoveredQuotaPolicy, type DiscoveryCodeStatus, type DiscoverySnapshot, type DiscoveryStatus, ERROR_MESSAGES_DE, ERROR_MESSAGES_EN, type EffectiveLimitsSnapshot, type ErrorMessageParams, FEATURE_NOT_LICENSED, type FeatureCatalogEntryRow, type FeatureDef, type FeatureKey, type FeatureNotLicensedBody, type FeatureRequiresIndex, type FeatureTier, type FeatureUiMeta, type FeatureUiRegistry, type FinalActivationResult, type FirstTimeCustomerCheck, type HandlePaymentEventInput, type HandlePaymentEventReason, type HandlePaymentEventResult, type ImmediatePlanChangeInput, type InvoiceLineItemSnapshot, type KpiCardDef, type KpiDisplayHint, MARKETING_PRIORITY_MAX, MARKETING_PRIORITY_MIN, type ManifestAccessPort, type ManifestContribution, type MarketingProjectionFilter, type MarketingProjectionRepository, type MarketingProjectionRow, type MarketingSettingsRepository, type MarketingSettingsRow, type MarketingTargetType, type MarketingTopFeature, type MfaPort, type NewContractLineItemData, OTP_RATE_LIMIT_MAX_SENDS, OTP_RATE_LIMIT_WINDOW_MINUTES, OTP_TTL_MINUTES, OTP_VERIFY_MAX_ATTEMPTS, type OnboardingPromoRedemption, type OnboardingSelectionRequest, type OnboardingSelectionResponse, PASSWORD_RESET_TTL_MINUTES, PENDING_CHECKOUT_TTL_DAYS, PENDING_EMAIL_TTL_HOURS, PENDING_ONBOARDING_TTL_DAYS, PLATFORM_ERROR_CODES, PROMO_ERROR_CODES, type Paginated, type PasswordHasher, type PasswordResetCliResult, type PaymentEventLog, type PaymentEventStatus, type PaymentProvider, type PendingRegistration, type PendingRegistrationCreateInput, type PendingRegistrationRepository, type PendingRegistrationSnapshot, type PendingRegistrationUpdateInput, type PersistenceCapabilities, PersistenceCapabilityError, type PersistenceClassRef, type PersistenceInjectionToken, type PersistenceProvider, type PlanCatalog, type PlanCatalogApp, type PlanCatalogImportReport, type PlanCatalogImportSink, type PlanCatalogLookup, type PlanCatalogMarketing, type PlanCatalogReadSink, type PlanCatalogReadSnapshot, type PlanDef, type PlanId, type PlanListFilter, type PlanRepository, type PlanRow, type PlanVersion, type PlanVersionFields, type PlanVersionMutationResult, type PlanVersionRecord, type PlanVersionRepository, type PlanVersionRow, type PlatformErrorBody, type PlatformErrorCode, type PlatformRole, type PlatformUserDto, PlatformUserExistsError, type ProjectPageDef, type PromoCode, type PromoCodeDurationType, type PromoCodeFilter, type PromoCodeRecord, type PromoCodeRedemption, type PromoCodeRedemptionListItem, type PromoCodeRedemptionRecord, type PromoCodeRedemptionRepository, type PromoCodeRedemptionStatus, type PromoCodeRepository, type PromoCodeStatsPort, type PromoCodeStatsSnapshot, type PromoCodeStatus, type PromoCodeValidationLog, type PromoCodeValidationLogRepository, type PromoCodeValidationResult, type PromoCodeValueType, type PromoErrorCode, type PromoPreviewInvalidReason, type PromoPreviewRequest, type PromoPreviewResponse, type PromoPreviewValidResponse, type PromoRevenueDeductionAggregator, type PromoSubscriptionLookup, type PromotionBillingCycle, type PromotionFilter, type PromotionI18n, type PromotionI18nFields, type PromotionRepository, type PromotionResult, type PromotionRow, type PromotionStatus, type PromotionTargetType, type PromotionType, type PromotionValue, type PublicBootResponse, type PublicComparisonRow, type PublicMarketingBundle, type PublicMarketingCatalogResponse, type PublicMarketingPlan, type PublicMarketingPromo, type PublicSignupPlan, type PublishBundleVersionData, type PublishPlanVersionData, type QuotaCatalogEntryRow, type QuotaEnforcementMode, type QuotaKey, type QuotaProvider, REGISTRATION_ERROR_CODES, REGISTRATION_RESUME_TTL_MINUTES, REGISTRATION_STEP_BY_STATUS, type ReassignTenantAdminCliResult, type RedeemPromoInTransactionCallback, type RegistrationAuditContext, type RegistrationAuditEvent, type RegistrationAuditEventType, type RegistrationAuditLogger, type RegistrationConfigSelection, type RegistrationConfiguratorLookup, type RegistrationErrorCode, type RegistrationOtpDelivery, type RegistrationPromoPreview, type RegistrationResumeDelivery, type RegistrationResumeTokenSigner, type RegistrationStatus, type RegistrationStep, type RequiredCapabilities, type ResumeRegistrationInput, type ResumeRegistrationResult, type ReviewCatalogEntryData, type RlsBypassPort, SETUP_ERROR_CODES, type SaaSiCatPersistenceAdapter, type SaaSiCatPersistenceAdminResources, type SaaSiCatPersistenceCatalog, type SaaSiCatPersistenceCore, type SaaSiCatPersistenceEntitlement, type SaaSiCatPersistencePromo, type SaaSiCatPersistenceTenantBilling, type SaveRegistrationConfigInput, type SaveRegistrationConfigResult, type ScheduledPlanChangeInput, type SelectPlanInput, type SelectPlanResult, type SelectableBundleShape, type SetCatalogEntryReviewData, type SetupConfirmMfaRequest, type SetupConfirmMfaResponse, type SetupErrorCode, type SetupRequest, type SetupResult, type SetupStatusResponse, type SlugAvailabilityCheck, type StandardPageDef, type StandardPageKey, type StartCheckoutInput, type StartCheckoutResult, type StartRegistrationInput, type StartRegistrationResult, type StrictModeWarning, type StrictModeWarningCode, type Subscription, type SubscriptionBundleRecord, type SubscriptionBundleRepository, type SubscriptionBundleView, type SubscriptionContractFilter, type SubscriptionContractInvoiceSnapshot, type SubscriptionContractPriceSnapshot, type SubscriptionContractRecord, type SubscriptionContractRepository, type SubscriptionContractStatus, type SubscriptionRecord, type SubscriptionRepository, type SubscriptionStatsPort, type SubscriptionStatsSnapshot, type SubscriptionStatus, type SubscriptionUsagePort, type SubscriptionUsageRecord, type SuperAdminProvisioningPort, type SyncDiscoveryResult, type TenantActionDef, type TenantColumnDef, type TenantDto, type TenantListFilter, type TenantPort, type TenantSubscriptionWritePort, type TerminateSubscriptionContractData, type TopPromoCode, type TransactionContext, type TransactionRunner, type UpdateBundleData, type UpdateBundleVersionDraftData, type UpdateCatalogEntryBaseData, type UpdateCatalogEntryI18nData, type UpdateCheckoutOfferData, type UpdateMarketingProjectionData, type UpdateMarketingSettingsData, type UpdatePlanData, type UpdatePlanVersionDraftData, type UpdatePromoCodeData, type UpdatePromoCodeRequest, type UpdatePromotionData, type UpsellOffer, type UpsellOfferResolver, type UpsertCapabilityEntryData, type UpsertFeatureCatalogEntryInput, type UpsertFeatureEntryData, type UpsertPlanInput, type UpsertPlanVersionInput, type UpsertQuotaEntryData, type UpsertResult, type UsageSnapshotPort, type UserAccountLookup, type UserListFilter, type UserManagementPort, type UserPort, type VerifyRegistrationOtpResult, type VersionChange, type VersionChangeDirection, type VersionEditability, type VersionEditableReason, type VersionedEntityBase, applyPromo, assertPersistenceCapabilities, buildActivePlanVersionWhere, buildActiveVersionWhere, buildFeatureRequiresIndex, classifyBundleVersionDiff, classifyPlanDiff, collectUnsatisfiedRequires, coverageExcludingSelf, formatErrorMessage, isBundleRedundant, isPlatformUserExistsError, isVersionEditable, missingRequiresFor, pickActivePromo, promoStatus, resolveBundleAvailability, resolveErrorMessage, selectChargeableBundles, startOfUtcDay };
5185
+ export { ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES, AUTH_ERROR_CODES, type ActionKey, type ActivationOrchestrator, type ActivePlanVersionWhere, type ActivePlanVersionWhereWithEndsAt, type ActiveVersionWhere, type ActiveVersionWhereWithEndsAt, type ActorTag, type AdminActor, type AdminAuditListFilter, type AdminManifest, type AdminResourcesPort, type AdminSubscriptionListRow, type AdminTenantDetail, type AdminTenantListFilter, type AdminTenantListRow, type AdminTenantStateResult, type AdminUserListFilter, type AdminUserListRow, type ApplyOnboardingSelectionInput, type ApplyOnboardingSelectionResult, type ApprovedCatalogKeys, type AuditActionDef, type AuditEntry, type AuditPort, type AuditQuery, type AuditQueryPort, type AuditStatsPort, type AuditStatsSnapshot, type AuthErrorCode, BILLING_ERROR_CODES, BUNDLE_PRICE_LOOKUP_LIMIT, type BillingCycle, type BillingErrorCode, type BundleAvailabilityState, type BundleCompatibility, type BundleFeatureShape, type BundleListFilter, type BundlePricingOverride, type BundleRepository, type BundleRow, type BundleVersionFields, type BundleVersionMutationResult, type BundleVersionRow, CATALOG_ERROR_CODES, CONTRACT_ERROR_CODES, type CancelSubscriptionBundleData, type CancelSubscriptionInput, type CancelSubscriptionResult, type CanonicalContractLineItemRow, type CanonicalContractRow, type CanonicalPlanRow, type CanonicalPlanVersionRow, type CapabilityCatalogEntryRow, type CapabilityCodeStatus, type CapabilityKey, type CapabilityKind, type CatalogEntryFilter, type CatalogEntryI18n, type CatalogEntryI18nFields, type CatalogEntryRepository, type CatalogErrorCode, type ChangeDirection, type CheckoutOfferFilter, type CheckoutOfferLineItem, type CheckoutOfferLineItemKind, type CheckoutOfferPriceBreakdown, type CheckoutOfferPromoCodeSnapshot, type CheckoutOfferPromotionSnapshot, type CheckoutOfferRepository, type CheckoutOfferRow, type CheckoutOfferStatus, type CheckoutSession, type CleanupResult, type CliUserRow, type ComponentKey, type ConfiguratorCatalog, type ConfiguratorMarketingProvider, type ConfiguratorModel, type ConfiguratorPlanMarketing, type ConfiguratorPlanVersionRow, type ConfiguratorPriceBreakdown, type ConfiguratorSourcesLookup, type ContractErrorCode, type ContractLineItemKind, type ContractLineItemRecord, type CreateBundleData, type CreateBundleVersionDraftData, type CreateCheckoutOfferData, type CreateMarketingProjectionData, type CreatePlanData, type CreatePlanVersionDraftData, type CreatePromoCodeData, type CreatePromoCodeRequest, type CreatePromotionData, type CreateSubscriptionBundleData, type CreateSubscriptionContractData, type CreateSuperAdminCliInput, type CreateTenantInput, type DiffResult, type DiscoveredCapability, type DiscoveredFeature, type DiscoveredQuota, type DiscoveredQuotaPolicy, type DiscoveryCodeStatus, type DiscoverySnapshot, type DiscoveryStatus, ERROR_MESSAGES_DE, ERROR_MESSAGES_EN, type EffectiveLimitsSnapshot, type ErrorMessageParams, FEATURE_NOT_LICENSED, type FeatureCatalogEntryRow, type FeatureDef, type FeatureKey, type FeatureNotLicensedBody, type FeatureRequiresIndex, type FeatureTier, type FeatureUiMeta, type FeatureUiRegistry, type FinalActivationResult, type FirstTimeCustomerCheck, type HandlePaymentEventInput, type HandlePaymentEventReason, type HandlePaymentEventResult, type ImmediatePlanChangeInput, type InvoiceLineItemSnapshot, type KpiCardDef, type KpiDisplayHint, MARKETING_PRIORITY_MAX, MARKETING_PRIORITY_MIN, type ManifestAccessPort, type ManifestContribution, type MarketingProjectionFilter, type MarketingProjectionRepository, type MarketingProjectionRow, type MarketingSettingsRepository, type MarketingSettingsRow, type MarketingTargetType, type MarketingTopFeature, type MfaPort, type NewContractLineItemData, OTP_RATE_LIMIT_MAX_SENDS, OTP_RATE_LIMIT_WINDOW_MINUTES, OTP_TTL_MINUTES, OTP_VERIFY_MAX_ATTEMPTS, type OnboardingPromoRedemption, type OnboardingSelectionRequest, type OnboardingSelectionResponse, PASSWORD_RESET_TTL_MINUTES, PENDING_CHECKOUT_TTL_DAYS, PENDING_EMAIL_TTL_HOURS, PENDING_ONBOARDING_TTL_DAYS, PLATFORM_ERROR_CODES, PROMO_ERROR_CODES, type Paginated, type PasswordHasher, type PasswordResetCliResult, type PaymentEventLog, type PaymentEventStatus, type PaymentProvider, type PendingRegistration, type PendingRegistrationCreateInput, type PendingRegistrationRepository, type PendingRegistrationSnapshot, type PendingRegistrationUpdateInput, type PersistenceCapabilities, PersistenceCapabilityError, type PersistenceClassRef, type PersistenceInjectionToken, type PersistenceProvider, type PlanCatalog, type PlanCatalogApp, type PlanCatalogImportReport, type PlanCatalogImportSink, type PlanCatalogLookup, type PlanCatalogMarketing, type PlanCatalogReadSink, type PlanCatalogReadSnapshot, type PlanDef, type PlanId, type PlanListFilter, type PlanRepository, type PlanRow, type PlanVersion, type PlanVersionFields, type PlanVersionMappingFields, type PlanVersionMutationResult, type PlanVersionRecord, type PlanVersionRepository, type PlanVersionRow, type PlatformErrorBody, type PlatformErrorCode, type PlatformRole, type PlatformUserDto, PlatformUserExistsError, type ProjectPageDef, type PromoCode, type PromoCodeDurationType, type PromoCodeFilter, type PromoCodeRecord, type PromoCodeRedemption, type PromoCodeRedemptionListItem, type PromoCodeRedemptionRecord, type PromoCodeRedemptionRepository, type PromoCodeRedemptionStatus, type PromoCodeRepository, type PromoCodeStatsPort, type PromoCodeStatsSnapshot, type PromoCodeStatus, type PromoCodeValidationLog, type PromoCodeValidationLogRepository, type PromoCodeValidationResult, type PromoCodeValueType, type PromoErrorCode, type PromoPreviewInvalidReason, type PromoPreviewRequest, type PromoPreviewResponse, type PromoPreviewValidResponse, type PromoRevenueDeductionAggregator, type PromoSubscriptionLookup, type PromotionBillingCycle, type PromotionI18n, type PromotionI18nFields, type PromotionRepository, type PromotionResult, type PromotionRow, type PromotionStatus, type PromotionTargetType, type PromotionType, type PromotionValue, type PublicBootResponse, type PublicComparisonRow, type PublicMarketingBundle, type PublicMarketingCatalogResponse, type PublicMarketingPlan, type PublicMarketingPromo, type PublicSignupPlan, type PublishBundleVersionData, type PublishBundleVersionMeta, type PublishPlanVersionData, type QuotaCatalogEntryRow, type QuotaEnforcementMode, type QuotaKey, type QuotaProvider, REGISTRATION_ERROR_CODES, REGISTRATION_RESUME_TTL_MINUTES, REGISTRATION_STEP_BY_STATUS, type ReassignTenantAdminCliResult, type RedeemPromoInTransactionCallback, type RegistrationAuditContext, type RegistrationAuditEvent, type RegistrationAuditEventType, type RegistrationAuditLogger, type RegistrationConfigSelection, type RegistrationConfiguratorLookup, type RegistrationErrorCode, type RegistrationOtpDelivery, type RegistrationPromoPreview, type RegistrationResumeDelivery, type RegistrationResumeTokenSigner, type RegistrationStatus, type RegistrationStep, type RequiredCapabilities, type ResumeRegistrationInput, type ResumeRegistrationResult, type ReviewCatalogEntryData, type RlsBypassPort, SETUP_ERROR_CODES, type SaaSiCatPersistenceAdapter, type SaaSiCatPersistenceAdminResources, type SaaSiCatPersistenceCatalog, type SaaSiCatPersistenceCore, type SaaSiCatPersistenceEntitlement, type SaaSiCatPersistencePromo, type SaaSiCatPersistenceTenantBilling, type SaveRegistrationConfigInput, type SaveRegistrationConfigResult, type ScheduledPlanChangeInput, type SelectPlanInput, type SelectPlanResult, type SelectableBundleShape, type SetCatalogEntryReviewData, type SetupConfirmMfaRequest, type SetupConfirmMfaResponse, type SetupErrorCode, type SetupRequest, type SetupResult, type SetupStatusResponse, type SlugAvailabilityCheck, type StandardPageDef, type StandardPageKey, type StartCheckoutInput, type StartCheckoutResult, type StartRegistrationInput, type StartRegistrationResult, type StoredBundleStem, type StrictModeWarning, type StrictModeWarningCode, type Subscription, type SubscriptionBundleRecord, type SubscriptionBundleRepository, type SubscriptionBundleView, type SubscriptionContractFilter, type SubscriptionContractInvoiceSnapshot, type SubscriptionContractPriceSnapshot, type SubscriptionContractRecord, type SubscriptionContractRepository, type SubscriptionContractStatus, type SubscriptionRecord, type SubscriptionRepository, type SubscriptionStatsPort, type SubscriptionStatsSnapshot, type SubscriptionStatus, type SubscriptionUsagePort, type SubscriptionUsageRecord, type SuperAdminProvisioningPort, type SyncDiscoveryResult, type TenantActionDef, type TenantColumnDef, type TenantDto, type TenantListFilter, type TenantPort, type TenantSubscriptionWritePort, type TerminateSubscriptionContractData, type TopPromoCode, type TransactionContext, type TransactionRunner, type UpdateBundleData, type UpdateBundleVersionDraftData, type UpdateCatalogEntryBaseData, type UpdateCatalogEntryI18nData, type UpdateCheckoutOfferData, type UpdateMarketingProjectionData, type UpdateMarketingSettingsData, type UpdatePlanData, type UpdatePlanVersionDraftData, type UpdatePromoCodeData, type UpdatePromoCodeRequest, type UpdatePromotionData, type UpsellOffer, type UpsellOfferResolver, type UpsertCapabilityEntryData, type UpsertFeatureCatalogEntryInput, type UpsertFeatureEntryData, type UpsertPlanInput, type UpsertPlanVersionInput, type UpsertQuotaEntryData, type UpsertResult, type UsageSnapshotPort, type UserAccountLookup, type UserListFilter, type UserManagementPort, type UserPort, type VerifyRegistrationOtpResult, type VersionChange, type VersionChangeDirection, type VersionEditability, type VersionEditableReason, type VersionedEntityBase, applyPromo, assertPersistenceCapabilities, buildActivePlanVersionWhere, buildActiveVersionWhere, buildFeatureRequiresIndex, bundleDraftDefaults, bundleStemDefaults, classifyBundleVersionDiff, classifyPlanDiff, collectUnsatisfiedRequires, coverageExcludingSelf, definedFields, formatErrorMessage, isBundleRedundant, isPlatformUserExistsError, isVersionEditable, missingRequiresFor, pickActivePromo, previousUtcDay, promoStatus, resolveBundleAvailability, resolveErrorMessage, selectChargeableBundles, startOfUtcDay, toBundleStemRow, toContractLineItemRecord, toPlanRow, toPlanVersionRow, toSubscriptionContractRecord };