@saasicat/core 1.0.0-rc.5 → 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;
@@ -1116,73 +967,330 @@ interface UpdateBundleVersionDraftData {
1116
967
  validFrom?: string | null;
1117
968
  validUntil?: string | null;
1118
969
  }
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;
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';
1156
1223
  }
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;
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';
1175
1229
  }
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[];
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
+ };
1246
+ }
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;
@@ -2505,10 +2617,22 @@ interface CreateSubscriptionContractData {
2505
2617
  }
2506
2618
  interface TerminateSubscriptionContractData {
2507
2619
  effectiveUntil: Date;
2508
- status: Extract<SubscriptionContractStatus, 'terminated' | 'superseded'>;
2620
+ /**
2621
+ * The terminal status, or `null` to end the contract by date alone.
2622
+ *
2623
+ * `findActiveByTenantId` already asks its question as a window —
2624
+ * `effectiveFrom <= asOf` and `effectiveUntil` null or after it — so a
2625
+ * contract given an end in the FUTURE is found until that moment and not
2626
+ * afterwards, with no scheduled job to flip anything.
2627
+ *
2628
+ * Writing a terminal status instead makes the contract disappear from that
2629
+ * lookup at once, which for a cancellation declared months ahead removes an
2630
+ * agreement the customer is still under. Null is how a caller says "it ends
2631
+ * then", and a status is how it says "it is over now".
2632
+ */
2633
+ status: Extract<SubscriptionContractStatus, 'terminated' | 'superseded'> | null;
2509
2634
  }
2510
2635
  interface SubscriptionContractFilter {
2511
- projectKey?: string;
2512
2636
  tenantId?: string;
2513
2637
  status?: SubscriptionContractStatus;
2514
2638
  asOf?: Date;
@@ -2530,7 +2654,6 @@ interface InvoiceLineItemSnapshot {
2530
2654
  }
2531
2655
  interface SubscriptionContractInvoiceSnapshot {
2532
2656
  contractId: string;
2533
- projectKey: string;
2534
2657
  tenantId: string;
2535
2658
  originalOfferId: string | null;
2536
2659
  currency: string;
@@ -2564,6 +2687,24 @@ interface SubscriptionRecord {
2564
2687
  } | null;
2565
2688
  planVersionId: string;
2566
2689
  planVersion: PlanVersionRecord;
2690
+ /**
2691
+ * When a cancellation was declared, and when it takes effect.
2692
+ *
2693
+ * Required, and required together, because entitlement resolution ends a
2694
+ * subscription by reading them: without the second date it cannot tell a
2695
+ * subscription that ends next January from one that ended last January, and
2696
+ * it grants the latter everything. Nothing else in the platform would
2697
+ * notice — no repository filters a cancelled subscription out, and stopping
2698
+ * the billing period is a different decision from ending what a tenant may
2699
+ * do.
2700
+ *
2701
+ * `null` on both means no cancellation. On a row written before the two
2702
+ * fields separated, `canceledAt` carries the effective date and
2703
+ * `canceledEffectiveAt` is genuinely null; every reader in the platform
2704
+ * applies `canceledEffectiveAt ?? canceledAt` for that reason.
2705
+ */
2706
+ canceledAt: Date | null;
2707
+ canceledEffectiveAt: Date | null;
2567
2708
  }
2568
2709
  /** Snapshot of a `PlanVersion` row. */
2569
2710
  interface PlanVersionRecord {
@@ -2620,11 +2761,10 @@ interface SubscriptionRepository {
2620
2761
  countByBundleVersionId?(bundleVersionId: string): Promise<number>;
2621
2762
  /**
2622
2763
  * Counts active subscriptions (status `ACTIVE` or `TRIAL`) per plan key,
2623
- * platform-wide across all tenants of the project — feeds the tenant
2624
- * 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`).
2625
2766
  * Cross-version: counts the plan, not a single PlanVersion
2626
- * (subscriptions on superseded versions are included). `projectKey` is
2627
- * informational for single-project consumers.
2767
+ * (subscriptions on superseded versions are included).
2628
2768
  *
2629
2769
  * Returns a map `planKey → count`; plans without an active subscription
2630
2770
  * are missing (UI defaults to 0). Platform-wide count across all tenants →
@@ -2632,7 +2772,7 @@ interface SubscriptionRepository {
2632
2772
  *
2633
2773
  * Optional — if not implemented, the tenant column stays 0.
2634
2774
  */
2635
- countActiveByPlanKey?(projectKey: string): Promise<Record<string, number>>;
2775
+ countActiveByPlanKey?(): Promise<Record<string, number>>;
2636
2776
  }
2637
2777
  /**
2638
2778
  * Adapter for the `subscription_bundles` junction.
@@ -2724,6 +2864,40 @@ interface SubscriptionUsageRecord {
2724
2864
  /** Current period window — for proration and change-effective date. */
2725
2865
  currentPeriodStart: Date | null;
2726
2866
  currentPeriodEnd: Date | null;
2867
+ /**
2868
+ * End of what was committed to, which the period end need not equal.
2869
+ *
2870
+ * The cancellation rules measure against this: a subscription cancelled
2871
+ * inside its term keeps running until the term ends, not until the period
2872
+ * does. Null on a trial, and on any subscription written before the field
2873
+ * existed — readers treat that as "the period end is the answer".
2874
+ */
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;
2889
+ /**
2890
+ * When a cancellation was declared, and when it lands.
2891
+ *
2892
+ * Required for the reason the same pair is required on
2893
+ * `SubscriptionRecord`: the tenant billing route reads them to refuse a
2894
+ * plan change on a subscription that has ended, and a record that omits
2895
+ * them answers "not cancelled" — so the change is applied and prorated
2896
+ * while entitlement resolution, which reads a record that does carry them,
2897
+ * grants nothing.
2898
+ */
2899
+ canceledAt: Date | null;
2900
+ canceledEffectiveAt: Date | null;
2727
2901
  pendingPlan: string | null;
2728
2902
  pendingBillingCycle: string | null;
2729
2903
  pendingEffectiveAt: Date | null;
@@ -2799,12 +2973,29 @@ interface ImmediatePlanChangeInput {
2799
2973
  * change, or target package without trial). A `Date` is persisted.
2800
2974
  */
2801
2975
  trialEndsAt?: Date | null;
2976
+ /**
2977
+ * `canceledAt` as the caller read it, so the write can claim the row only
2978
+ * while that is still true.
2979
+ *
2980
+ * Three of the plan route's decisions depend on the cancellation — whether
2981
+ * the change is refused at all, whether the billing cycle may move, and
2982
+ * whether a fresh period is opened — and a read and a write are two
2983
+ * moments. A cancellation declared in between made every one of them answer
2984
+ * about a state that no longer existed, and the write went ahead anyway: a
2985
+ * plan term recorded past the date the subscription ends.
2986
+ *
2987
+ * `null` is a value here rather than an absence. It claims a row that has
2988
+ * no cancellation, and loses against one that has acquired one.
2989
+ */
2990
+ expectedCanceledAt: Date | null;
2802
2991
  }
2803
2992
  /** Input for `schedulePlanChange` (change at period end). */
2804
2993
  interface ScheduledPlanChangeInput {
2805
2994
  pendingPlan: string;
2806
2995
  pendingBillingCycle: string;
2807
2996
  pendingEffectiveAt: Date;
2997
+ /** See `ImmediatePlanChangeInput.expectedCanceledAt`. */
2998
+ expectedCanceledAt: Date | null;
2808
2999
  }
2809
3000
  /**
2810
3001
  * Input for `applyOnboardingSelection`. Plan-change fields that the
@@ -2813,6 +3004,12 @@ interface ScheduledPlanChangeInput {
2813
3004
  interface ApplyOnboardingSelectionInput {
2814
3005
  planId: string;
2815
3006
  cycle: string;
3007
+ /**
3008
+ * See `ImmediatePlanChangeInput.expectedCanceledAt`. The atomic path needs
3009
+ * it for the same reason the sequential one does: without it, the preferred
3010
+ * implementation is the one where the race stays open.
3011
+ */
3012
+ expectedCanceledAt: Date | null;
2816
3013
  /** For TRIAL → null, otherwise period start from `initialPeriodWindow`. */
2817
3014
  periodStart: Date | null;
2818
3015
  periodEnd: Date | null;
@@ -2829,6 +3026,12 @@ interface ApplyOnboardingSelectionResult {
2829
3026
  subscriptionId: string;
2830
3027
  /** null if no redeemPromo callback was provided or the callback returned null. */
2831
3028
  promoRedemption: PromoCodeRedemptionRecord | null;
3029
+ /**
3030
+ * False when the row's cancellation moved since the caller read it, in
3031
+ * which case nothing was written — including the promo redemption, which
3032
+ * shares the transaction.
3033
+ */
3034
+ claimed: boolean;
2832
3035
  }
2833
3036
  /**
2834
3037
  * Callback signature for promo-code redemption WITHIN the onboarding
@@ -2846,14 +3049,46 @@ type RedeemPromoInTransactionCallback = (tx: TransactionContext, subscriptionId:
2846
3049
  * app-specific. The platform service calls `invalidateTenant` in the
2847
3050
  * EntitlementService after a successful adapter call.
2848
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
+ }
2849
3080
  interface TenantSubscriptionWritePort {
2850
3081
  /** Immediate change: set plan + cycle, clear pending fields, optionally reset the period. */
2851
3082
  changePlanImmediate(tenantId: string, input: ImmediatePlanChangeInput): Promise<{
2852
3083
  plan: string;
2853
3084
  billingCycle: string;
3085
+ /** False when the row's cancellation moved since the caller read it. */
3086
+ claimed: boolean;
2854
3087
  }>;
2855
3088
  /** Change at period end: set pending fields. */
2856
- schedulePlanChange(tenantId: string, input: ScheduledPlanChangeInput): Promise<void>;
3089
+ schedulePlanChange(tenantId: string, input: ScheduledPlanChangeInput): Promise<{
3090
+ claimed: boolean;
3091
+ }>;
2857
3092
  /**
2858
3093
  * Marks the pending PlanVersion as accepted. Idempotent — a duplicate
2859
3094
  * accept is a no-op. Returns `alreadyAccepted: true` if the status was
@@ -2866,13 +3101,30 @@ interface TenantSubscriptionWritePort {
2866
3101
  alreadyAccepted: boolean;
2867
3102
  }>;
2868
3103
  /**
2869
- * Cancel the subscription. `immediate=true` → status CANCELED from now;
2870
- * `false` → canceledAt = currentPeriodEnd, status is preserved.
3104
+ * Record a cancellation. The dates are decided above this port.
3105
+ *
3106
+ * `canceledAt` is when the customer said it; `effectiveAt` is when it
3107
+ * lands. They differ for every ordinary cancellation, because a
3108
+ * subscription cancelled inside its term keeps running, keeps being billed
3109
+ * and keeps its entitlements until the term ends. An adapter that computed
3110
+ * the second from the first — which this one did, as
3111
+ * `immediate ? now : currentPeriodEnd` — was deciding a commercial
3112
+ * question in a persistence layer, and could not see the minimum term or
3113
+ * the notice period at all.
3114
+ *
3115
+ * `terminateNow` flips the status immediately, and is set when the
3116
+ * cancellation is already effective: an operator ending a contract, or the
3117
+ * rules finding nothing left to run — no period, no term, as on a trial.
3118
+ * It is never a client's request. A tenant may always declare a
3119
+ * cancellation and may never shorten the term they are in; what decides
3120
+ * this flag is the date the rules returned, not the date they asked for.
3121
+ *
3122
+ * `minimumTermUntil` extends the stored commitment, and is set only when
3123
+ * the cancellation itself extends it: a declaration made after the notice
3124
+ * deadline buys the following period. Left unset the stored term end is
3125
+ * unchanged, which is the ordinary case.
2871
3126
  */
2872
- cancelSubscription(tenantId: string, immediate: boolean, now: Date): Promise<{
2873
- canceledAt: Date | null;
2874
- status: string;
2875
- }>;
3127
+ cancelSubscription(tenantId: string, input: CancelSubscriptionInput): Promise<CancelSubscriptionResult>;
2876
3128
  /**
2877
3129
  * Atomic onboarding creation: sets plan + cycle + period window
2878
3130
  * AND optionally calls a promo-redeem callback — all in a
@@ -3124,7 +3376,6 @@ interface RlsBypassPort {
3124
3376
 
3125
3377
  /** Filter for `PlanRepository.list()`. */
3126
3378
  interface PlanListFilter {
3127
- projectKey: string;
3128
3379
  /** Exclude soft-deleted plans — default `true`. */
3129
3380
  excludeDeleted?: boolean;
3130
3381
  /**
@@ -3158,7 +3409,7 @@ interface PlanListFilter {
3158
3409
  interface PlanRepository {
3159
3410
  list(filter: PlanListFilter): Promise<PlanRow[]>;
3160
3411
  findById(planId: string): Promise<PlanRow | null>;
3161
- findByKey(projectKey: string, planKey: string): Promise<PlanRow | null>;
3412
+ findByKey(planKey: string): Promise<PlanRow | null>;
3162
3413
  create(data: CreatePlanData): Promise<PlanRow>;
3163
3414
  update(planId: string, data: UpdatePlanData): Promise<PlanRow>;
3164
3415
  /** Sets `deletedAt` to NOW(); soft-deleted plans are filtered from `list` by default. */
@@ -3259,10 +3510,24 @@ interface PlanRepository {
3259
3510
  }
3260
3511
  /** Filter for `BundleRepository.list()`. */
3261
3512
  interface BundleListFilter {
3262
- projectKey: string;
3263
3513
  /** Exclude soft-deleted bundles — default `true`. */
3264
3514
  excludeDeleted?: boolean;
3265
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
+ }
3266
3531
  /**
3267
3532
  * Adapter for `Bundle` + `BundleVersion` persistence. Consumers implement
3268
3533
  * this against their Prisma tables (`bundles` + `bundle_versions`).
@@ -3281,7 +3546,7 @@ interface BundleListFilter {
3281
3546
  interface BundleRepository {
3282
3547
  list(filter: BundleListFilter): Promise<BundleRow[]>;
3283
3548
  findById(bundleId: string): Promise<BundleRow | null>;
3284
- findByKey(projectKey: string, bundleKey: string): Promise<BundleRow | null>;
3549
+ findByKey(bundleKey: string): Promise<BundleRow | null>;
3285
3550
  create(data: CreateBundleData): Promise<BundleRow>;
3286
3551
  update(bundleId: string, data: UpdateBundleData): Promise<BundleRow>;
3287
3552
  /** Sets `deletedAt` to NOW(); soft-deleted bundles are filtered from `list` by default. */
@@ -3340,14 +3605,7 @@ interface BundleRepository {
3340
3605
  * if the predecessor carries a `validUntil` — the adapter only
3341
3606
  * persists, it does not validate again.
3342
3607
  */
3343
- publishDraft(versionId: string, publishMeta: {
3344
- publishedByUserId: string | null;
3345
- publishedChanges: VersionChange[];
3346
- nonRegressive: boolean;
3347
- /** Required — validated by the service before the repository call. */
3348
- validFrom: Date;
3349
- validUntil: Date | null;
3350
- }, tx?: TransactionContext): Promise<BundleVersionRow>;
3608
+ publishDraft(versionId: string, publishMeta: PublishBundleVersionMeta, tx?: TransactionContext): Promise<BundleVersionRow>;
3351
3609
  /**
3352
3610
  * Hard-discards a draft version (`publishedAt === null`) from the DB.
3353
3611
  * Throws if the version was already published — published versions
@@ -3385,7 +3643,6 @@ interface MarketingProjectionRepository {
3385
3643
  }
3386
3644
  /** Upsert input for a capability from the discovery sync. */
3387
3645
  interface UpsertCapabilityEntryData {
3388
- projectKey: string;
3389
3646
  capabilityKey: string;
3390
3647
  label: string;
3391
3648
  description: string | null;
@@ -3402,7 +3659,6 @@ interface UpsertCapabilityEntryData {
3402
3659
  }
3403
3660
  /** Upsert input for a feature from the discovery sync. */
3404
3661
  interface UpsertFeatureEntryData {
3405
- projectKey: string;
3406
3662
  featureKey: string;
3407
3663
  label: string;
3408
3664
  description: string | null;
@@ -3416,7 +3672,6 @@ interface UpsertFeatureEntryData {
3416
3672
  }
3417
3673
  /** Upsert input for a quota from the discovery sync. */
3418
3674
  interface UpsertQuotaEntryData {
3419
- projectKey: string;
3420
3675
  quotaKey: string;
3421
3676
  label: string;
3422
3677
  description: string | null;
@@ -3446,7 +3701,7 @@ interface SetCatalogEntryReviewData {
3446
3701
  * Prisma tables.
3447
3702
  *
3448
3703
  * Binding:
3449
- * - `upsert*` matches on (`projectKey`, `<key>`) and leaves `i18n`,
3704
+ * - `upsert*` matches on `<key>` and leaves `i18n`,
3450
3705
  * `sortOrder`, `createdAt` as well as the approval fields (`approvedAt`/
3451
3706
  * `approvedBy`/`approvedSignature`) **untouched** on an update —
3452
3707
  * only the code-derived fields + the status (resolved by the service)
@@ -3462,7 +3717,7 @@ interface CatalogEntryRepository {
3462
3717
  upsertCapability(data: UpsertCapabilityEntryData): Promise<CapabilityCatalogEntryRow>;
3463
3718
  upsertFeature(data: UpsertFeatureEntryData): Promise<FeatureCatalogEntryRow>;
3464
3719
  upsertQuota(data: UpsertQuotaEntryData): Promise<QuotaCatalogEntryRow>;
3465
- retireMissing(projectKey: string, type: 'capability' | 'feature' | 'quota', presentKeys: string[]): Promise<number>;
3720
+ retireMissing(type: 'capability' | 'feature' | 'quota', presentKeys: string[]): Promise<number>;
3466
3721
  /**
3467
3722
  * Sets or clears the successor pointer of a feature/quota
3468
3723
  * (#39). The sync calls this when a key disappears from the snapshot
@@ -3471,17 +3726,17 @@ interface CatalogEntryRepository {
3471
3726
  * adapters without a `successor_key` column omit the methods, and the sync
3472
3727
  * then skips the pointers with a warn log.
3473
3728
  */
3474
- setFeatureSuccessor?(projectKey: string, featureKey: string, successorKey: string | null): Promise<FeatureCatalogEntryRow>;
3475
- setQuotaSuccessor?(projectKey: string, quotaKey: string, successorKey: string | null): Promise<QuotaCatalogEntryRow>;
3476
- findFeature(projectKey: string, featureKey: string): Promise<FeatureCatalogEntryRow | null>;
3477
- findQuota(projectKey: string, quotaKey: string): Promise<QuotaCatalogEntryRow | null>;
3478
- setFeatureReview(projectKey: string, featureKey: string, data: SetCatalogEntryReviewData): Promise<FeatureCatalogEntryRow>;
3479
- setQuotaReview(projectKey: string, quotaKey: string, data: SetCatalogEntryReviewData): Promise<QuotaCatalogEntryRow>;
3480
- setFeatureI18n(projectKey: string, featureKey: string, i18n: CatalogEntryI18n): Promise<FeatureCatalogEntryRow>;
3481
- 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>;
3482
3737
  /** Sets the editable base fields (default locale `label`/`description`). */
3483
- setFeatureBase(projectKey: string, featureKey: string, data: UpdateCatalogEntryBaseData): Promise<FeatureCatalogEntryRow>;
3484
- 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>;
3485
3740
  }
3486
3741
  /**
3487
3742
  * Adapter for `promotions`. **No versioning** — promotions are edited
@@ -3489,7 +3744,7 @@ interface CatalogEntryRepository {
3489
3744
  * this against their `promotions` Prisma table.
3490
3745
  */
3491
3746
  interface PromotionRepository {
3492
- list(filter: PromotionFilter): Promise<PromotionRow[]>;
3747
+ list(): Promise<PromotionRow[]>;
3493
3748
  findById(id: string): Promise<PromotionRow | null>;
3494
3749
  create(data: CreatePromotionData): Promise<PromotionRow>;
3495
3750
  update(id: string, data: UpdatePromotionData): Promise<PromotionRow>;
@@ -3497,13 +3752,14 @@ interface PromotionRepository {
3497
3752
  delete(id: string): Promise<void>;
3498
3753
  }
3499
3754
  /**
3500
- * Adapter for `marketing_settings` — one row per project. `get` returns
3501
- * `null` as long as the SuperAdmin has saved nothing (then the full
3502
- * `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.
3503
3759
  */
3504
3760
  interface MarketingSettingsRepository {
3505
- get(projectKey: string): Promise<MarketingSettingsRow | null>;
3506
- upsert(projectKey: string, data: UpdateMarketingSettingsData): Promise<MarketingSettingsRow>;
3761
+ get(): Promise<MarketingSettingsRow | null>;
3762
+ upsert(data: UpdateMarketingSettingsData): Promise<MarketingSettingsRow>;
3507
3763
  }
3508
3764
 
3509
3765
  /**
@@ -3715,6 +3971,8 @@ declare const CATALOG_ERROR_CODES: {
3715
3971
  readonly BUNDLE_VERSION_SUPERSEDED: "BUNDLE_VERSION_SUPERSEDED";
3716
3972
  readonly BUNDLE_VERSION_REGRESSION: "BUNDLE_VERSION_REGRESSION";
3717
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";
3718
3976
  readonly BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED";
3719
3977
  readonly BUNDLE_VERSION_VALID_FROM_REQUIRED: "BUNDLE_VERSION_VALID_FROM_REQUIRED";
3720
3978
  readonly BUNDLE_VERSION_VALID_FROM_INVALID: "BUNDLE_VERSION_VALID_FROM_INVALID";
@@ -3759,6 +4017,8 @@ declare const BILLING_ERROR_CODES: {
3759
4017
  readonly BUNDLE_ALREADY_SUBSCRIBED: "BUNDLE_ALREADY_SUBSCRIBED";
3760
4018
  readonly BUNDLE_INCOMPATIBLE_WITH_PLAN: "BUNDLE_INCOMPATIBLE_WITH_PLAN";
3761
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";
3762
4022
  readonly SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED";
3763
4023
  readonly SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SUBSCRIPTION_BUNDLE_NOT_CANCELLED";
3764
4024
  readonly SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE";
@@ -3915,6 +4175,8 @@ declare const PLATFORM_ERROR_CODES: {
3915
4175
  readonly BUNDLE_ALREADY_SUBSCRIBED: "BUNDLE_ALREADY_SUBSCRIBED";
3916
4176
  readonly BUNDLE_INCOMPATIBLE_WITH_PLAN: "BUNDLE_INCOMPATIBLE_WITH_PLAN";
3917
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";
3918
4180
  readonly SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED: "SUBSCRIPTION_BUNDLE_ALREADY_CANCELLED";
3919
4181
  readonly SUBSCRIPTION_BUNDLE_NOT_CANCELLED: "SUBSCRIPTION_BUNDLE_NOT_CANCELLED";
3920
4182
  readonly SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE: "SUBSCRIPTION_BUNDLE_CANCELLATION_EFFECTIVE";
@@ -3968,6 +4230,8 @@ declare const PLATFORM_ERROR_CODES: {
3968
4230
  readonly BUNDLE_VERSION_SUPERSEDED: "BUNDLE_VERSION_SUPERSEDED";
3969
4231
  readonly BUNDLE_VERSION_REGRESSION: "BUNDLE_VERSION_REGRESSION";
3970
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";
3971
4235
  readonly BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED: "BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED";
3972
4236
  readonly BUNDLE_VERSION_VALID_FROM_REQUIRED: "BUNDLE_VERSION_VALID_FROM_REQUIRED";
3973
4237
  readonly BUNDLE_VERSION_VALID_FROM_INVALID: "BUNDLE_VERSION_VALID_FROM_INVALID";
@@ -4786,6 +5050,98 @@ interface SetupConfirmMfaResponse {
4786
5050
  ok: boolean;
4787
5051
  }
4788
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
+
4789
5145
  /** Why a version is editable (for UI badges + audit logs). */
4790
5146
  type VersionEditableReason = 'draft' | 'pre-active';
4791
5147
  interface VersionEditability {
@@ -4826,4 +5182,4 @@ declare function resolveErrorMessage(body: Partial<PlatformErrorBody> & Record<s
4826
5182
 
4827
5183
  declare const ERROR_MESSAGES_DE: Record<PlatformErrorCode, string>;
4828
5184
 
4829
- 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 };