@saasicat/nest 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +53 -0
  3. package/dist/admin/index.cjs +1283 -0
  4. package/dist/admin/index.d.cts +225 -0
  5. package/dist/admin/index.d.ts +225 -0
  6. package/dist/admin/index.js +58 -0
  7. package/dist/admin-audit.service-9IqXMlZm.d.cts +30 -0
  8. package/dist/admin-audit.service-9IqXMlZm.d.ts +30 -0
  9. package/dist/admin-manifest.config-DyrQNT7M.d.cts +10 -0
  10. package/dist/admin-manifest.config-DyrQNT7M.d.ts +10 -0
  11. package/dist/aggregation-CJ3qQf92.d.cts +288 -0
  12. package/dist/aggregation-Dvz9e8X2.d.ts +288 -0
  13. package/dist/billing/index.cjs +5205 -0
  14. package/dist/billing/index.d.cts +1326 -0
  15. package/dist/billing/index.d.ts +1326 -0
  16. package/dist/billing/index.js +197 -0
  17. package/dist/catalog/index.cjs +6449 -0
  18. package/dist/catalog/index.d.cts +1073 -0
  19. package/dist/catalog/index.d.ts +1073 -0
  20. package/dist/catalog/index.js +158 -0
  21. package/dist/checkout-offer/index.cjs +807 -0
  22. package/dist/checkout-offer/index.d.cts +120 -0
  23. package/dist/checkout-offer/index.d.ts +120 -0
  24. package/dist/checkout-offer/index.js +20 -0
  25. package/dist/chunk-2FR6ZL7R.js +0 -0
  26. package/dist/chunk-2PNX2QL2.js +347 -0
  27. package/dist/chunk-2VNZYRDV.js +326 -0
  28. package/dist/chunk-7MLGHWKO.js +12 -0
  29. package/dist/chunk-ABB2CRAS.js +31 -0
  30. package/dist/chunk-AJ3EPELS.js +157 -0
  31. package/dist/chunk-E56W4U2P.js +90 -0
  32. package/dist/chunk-HRU3CP57.js +71 -0
  33. package/dist/chunk-L4SUWH5B.js +14 -0
  34. package/dist/chunk-M6CZQYPY.js +314 -0
  35. package/dist/chunk-MDIZUVIK.js +87 -0
  36. package/dist/chunk-MV6LW5UB.js +217 -0
  37. package/dist/chunk-NYLON2VC.js +6 -0
  38. package/dist/chunk-P6MYZMXQ.js +951 -0
  39. package/dist/chunk-Q53N43LQ.js +13 -0
  40. package/dist/chunk-QJVPRD3R.js +573 -0
  41. package/dist/chunk-RIGLQ4BV.js +3367 -0
  42. package/dist/chunk-SABTXESR.js +24 -0
  43. package/dist/chunk-SHUYVCID.js +6 -0
  44. package/dist/chunk-SYEVGPFC.js +686 -0
  45. package/dist/chunk-V7HUZ6UQ.js +5923 -0
  46. package/dist/chunk-WPUVJRIN.js +29 -0
  47. package/dist/chunk-XCZ3TTFK.js +22 -0
  48. package/dist/chunk-XSGU2Y2O.js +1611 -0
  49. package/dist/chunk-YNEW3L63.js +190 -0
  50. package/dist/chunk-YXGHPILG.js +614 -0
  51. package/dist/chunk-ZL4LLE2Z.js +465 -0
  52. package/dist/di-CcNeq9v-.d.cts +16 -0
  53. package/dist/di-CcNeq9v-.d.ts +16 -0
  54. package/dist/discovery/index.cjs +588 -0
  55. package/dist/discovery/index.d.cts +299 -0
  56. package/dist/discovery/index.d.ts +299 -0
  57. package/dist/discovery/index.js +49 -0
  58. package/dist/discovery.scanner-CUYLKlYT.d.cts +59 -0
  59. package/dist/discovery.scanner-CUYLKlYT.d.ts +59 -0
  60. package/dist/entitlement/index.cjs +805 -0
  61. package/dist/entitlement/index.d.cts +60 -0
  62. package/dist/entitlement/index.d.ts +60 -0
  63. package/dist/entitlement/index.js +57 -0
  64. package/dist/index.cjs +15918 -0
  65. package/dist/index.d.cts +85 -0
  66. package/dist/index.d.ts +85 -0
  67. package/dist/index.js +956 -0
  68. package/dist/plan-resolution-CFCoUkrE.d.cts +41 -0
  69. package/dist/plan-resolution-CFCoUkrE.d.ts +41 -0
  70. package/dist/platform/index.cjs +2772 -0
  71. package/dist/platform/index.d.cts +118 -0
  72. package/dist/platform/index.d.ts +118 -0
  73. package/dist/platform/index.js +37 -0
  74. package/dist/promo/index.cjs +995 -0
  75. package/dist/promo/index.d.cts +121 -0
  76. package/dist/promo/index.d.ts +121 -0
  77. package/dist/promo/index.js +58 -0
  78. package/dist/registration/index.cjs +1687 -0
  79. package/dist/registration/index.d.cts +444 -0
  80. package/dist/registration/index.d.ts +444 -0
  81. package/dist/registration/index.js +82 -0
  82. package/dist/saas-platform.module-CgZ2omgg.d.cts +180 -0
  83. package/dist/saas-platform.module-DT9TnCzk.d.ts +180 -0
  84. package/dist/service-DX8KbGXl.d.cts +109 -0
  85. package/dist/service-DX8KbGXl.d.ts +109 -0
  86. package/dist/subscription-contract/index.cjs +471 -0
  87. package/dist/subscription-contract/index.d.cts +17 -0
  88. package/dist/subscription-contract/index.d.ts +17 -0
  89. package/dist/subscription-contract/index.js +21 -0
  90. package/dist/subscription-contract.service--cm47ZJJ.d.cts +39 -0
  91. package/dist/subscription-contract.service--cm47ZJJ.d.ts +39 -0
  92. package/dist/testing/index.cjs +3942 -0
  93. package/dist/testing/index.d.cts +279 -0
  94. package/dist/testing/index.d.ts +279 -0
  95. package/dist/testing/index.js +1195 -0
  96. package/package.json +185 -0
@@ -0,0 +1,1073 @@
1
+ import { BundleRepository, DiscoverySnapshot, SubscriptionRepository, PlanRepository, CatalogEntryRepository, BundleRow, BundleVersionRow, CreateBundleData, UpdateBundleData, CreateBundleVersionDraftData, BundleVersionMutationResult, UpdateBundleVersionDraftData, PublishBundleVersionData, BusinessTypeRepository, BusinessTypeRow, BusinessTypeVersionRow, CreateBusinessTypeData, UpdateBusinessTypeData, CreateBusinessTypeVersionDraftData, BusinessTypeVersionMutationResult, UpdateBusinessTypeVersionDraftData, PublishBusinessTypeVersionData, MarketingProjectionRepository, MarketingProjectionFilter, MarketingProjectionRow, CreateMarketingProjectionData, UpdateMarketingProjectionData, FeatureUiRegistry, CapabilityCatalogEntryRow, FeatureCatalogEntryRow, QuotaCatalogEntryRow, ReviewCatalogEntryData, CatalogEntryI18n, UpdateCatalogEntryBaseData, SyncDiscoveryResult, PromotionRepository, PromotionRow, CreatePromotionData, UpdatePromotionData, MarketingSettingsRepository, MarketingSettingsRow, UpdateMarketingSettingsData, PublicMarketingCatalogResponse, PlanRow, CreatePlanData, UpdatePlanData, PlanVersionRow, CreatePlanVersionDraftData, PlanVersionMutationResult, UpdatePlanVersionDraftData, PublishPlanVersionData, StrictModeWarningCode, StrictModeWarning, ApprovedCatalogKeys, DiscoveredQuota, DiscoveryStatus, PromotionType, PromotionValue, PromotionTargetType, PromotionBillingCycle, PromotionI18n } from '@saasicat/types';
2
+ import { c as DiscoveryScanner } from '../discovery.scanner-CUYLKlYT.cjs';
3
+ import { OnApplicationBootstrap, Type, CanActivate, DynamicModule, ForwardReference, Provider } from '@nestjs/common';
4
+ import { P as ProviderSpec } from '../di-CcNeq9v-.cjs';
5
+ import '@nestjs/core';
6
+
7
+ /**
8
+ * Configuration for the mutating operations.
9
+ */
10
+ interface CatalogServiceConfig {
11
+ /** Active mode for the strict-mode check. Default `'blocking'` (#12). */
12
+ strictModeCheckMode?: 'warn-only' | 'blocking';
13
+ /**
14
+ * Discovery→Catalog auto-sync at boot (#12). Default `true` — runs when a
15
+ * `DiscoverySnapshot` is available. Set to `false` to disable the boot
16
+ * sync; the manual `POST /admin/catalog/discovery/sync` is unaffected.
17
+ */
18
+ autoSyncDiscoveryAtBoot?: boolean;
19
+ /**
20
+ * Feature keys the catalog is deliberately allowed to carry WITHOUT them
21
+ * existing in the discovery snapshot (= in code via @ImplementsCapability) —
22
+ * marketed non-code features such as support SLAs (e.g. PRIORITY_SUPPORT).
23
+ * The strict-mode check does NOT report them as BUNDLE_/PLAN_FEATURE_UNKNOWN.
24
+ * Use sparingly: this is the only legitimate exception to "code is the
25
+ * source of truth"; not-yet-built features do NOT belong here but should be
26
+ * removed from the catalog until they are implemented.
27
+ */
28
+ marketedOnlyFeatures?: string[];
29
+ }
30
+ declare class BundlesService {
31
+ private readonly repo;
32
+ private readonly snapshot;
33
+ private readonly subscriptions;
34
+ private readonly planRepo;
35
+ private readonly scanner;
36
+ private readonly catalogEntries;
37
+ private readonly logger;
38
+ private mode;
39
+ private readonly marketedOnly;
40
+ constructor(repo: BundleRepository, snapshot?: DiscoverySnapshot | null, config?: CatalogServiceConfig, subscriptions?: SubscriptionRepository | null, planRepo?: PlanRepository | null, scanner?: DiscoveryScanner | null, catalogEntries?: CatalogEntryRepository | null);
41
+ listBundles(projectKey: string): Promise<BundleRow[]>;
42
+ getBundle(bundleId: string): Promise<{
43
+ bundle: BundleRow;
44
+ versions: BundleVersionRow[];
45
+ }>;
46
+ createBundle(data: CreateBundleData): Promise<BundleRow>;
47
+ updateBundle(bundleId: string, data: UpdateBundleData): Promise<BundleRow>;
48
+ softDeleteBundle(bundleId: string): Promise<void>;
49
+ /**
50
+ * Hard-deletes a draft BundleVersion (`publishedAt === null`) from the
51
+ * DB. Published versions stay immutable (contract-protection P1) — the
52
+ * service throws 422 with code `BUNDLE_VERSION_ALREADY_PUBLISHED`.
53
+ */
54
+ discardBundleDraft(versionId: string): Promise<void>;
55
+ listBundleVersions(bundleId: string): Promise<BundleVersionRow[]>;
56
+ getBundleVersion(versionId: string): Promise<BundleVersionRow>;
57
+ /**
58
+ * Annotates each BundleVersion with `isLatestInChain` (highest version
59
+ * number in the chain) + `subscriptionCount` so that UI and backend make
60
+ * the same editability decision. `subscriptionCount` stays `undefined`
61
+ * when no SubscriptionRepository is registered or the method was not
62
+ * implemented — `isVersionEditable` interprets that fail-closed
63
+ * (= frozen).
64
+ */
65
+ private annotateEditability;
66
+ /**
67
+ * Creates a new draft BundleVersion. Throws when a draft already exists
68
+ * for the same bundle (repository constraint).
69
+ *
70
+ * The strict-mode check runs on the input; in `warn-only` warnings are
71
+ * returned, in `blocking` the service throws HTTP 422.
72
+ */
73
+ createBundleDraft(data: CreateBundleVersionDraftData): Promise<BundleVersionMutationResult>;
74
+ /**
75
+ * Updates a draft version or — as a deliberate relaxation of
76
+ * contract-protection P1 — a published-but-future BundleVersion that is
77
+ * latest-in-chain and does not yet bind any subscription. Once
78
+ * `validFrom` is reached or a booking references it, the version freezes
79
+ * again. The concrete decision is made by the `isVersionEditable` helper,
80
+ * which is mirrored by the UI.
81
+ */
82
+ updateBundleDraft(versionId: string, data: UpdateBundleVersionDraftData): Promise<BundleVersionMutationResult>;
83
+ /**
84
+ * Publishes a draft version atomically. Steps:
85
+ * 1. Strict check on the draft definition (warn-only or blocking)
86
+ * 2. Enforce validFrom requirement + ordering against the predecessor
87
+ * (SPEC_V2 §4.2 + §11.1 M6 Pack 2c, analogous to PlanVersion)
88
+ * 3. Compute the diff against the predecessor version (latest live) →
89
+ * `publishedChanges` + `nonRegressive` via `classifyBundleVersionDiff`
90
+ * 4. Call Repository.publishDraft with the diff result + validity dates
91
+ * (transaction; auto-succession sets the previous validUntil)
92
+ */
93
+ publishBundleVersion(versionId: string, publishMeta: PublishBundleVersionData): Promise<BundleVersionMutationResult>;
94
+ private runStrictCheck;
95
+ private gateOrPass;
96
+ private assertBundleVersionWindowUpdate;
97
+ private findPreviousBundleVersion;
98
+ private parseOptionalBundleDate;
99
+ private parseRequiredBundleDate;
100
+ }
101
+
102
+ declare class BusinessTypesService {
103
+ private readonly repo;
104
+ private readonly bundleRepo;
105
+ private readonly snapshot;
106
+ private readonly scanner;
107
+ private readonly catalogEntries;
108
+ private readonly mode;
109
+ constructor(repo: BusinessTypeRepository, bundleRepo: BundleRepository, snapshot?: DiscoverySnapshot | null, config?: CatalogServiceConfig, scanner?: DiscoveryScanner | null, catalogEntries?: CatalogEntryRepository | null);
110
+ listBusinessTypes(projectKey: string): Promise<BusinessTypeRow[]>;
111
+ getBusinessType(businessTypeId: string): Promise<{
112
+ businessType: BusinessTypeRow;
113
+ versions: BusinessTypeVersionRow[];
114
+ }>;
115
+ createBusinessType(data: CreateBusinessTypeData): Promise<BusinessTypeRow>;
116
+ updateBusinessType(businessTypeId: string, data: UpdateBusinessTypeData): Promise<BusinessTypeRow>;
117
+ softDeleteBusinessType(businessTypeId: string): Promise<void>;
118
+ listBusinessTypeVersions(businessTypeId: string): Promise<BusinessTypeVersionRow[]>;
119
+ getBusinessTypeVersion(versionId: string): Promise<BusinessTypeVersionRow>;
120
+ createBusinessTypeDraft(data: CreateBusinessTypeVersionDraftData): Promise<BusinessTypeVersionMutationResult>;
121
+ updateBusinessTypeDraft(versionId: string, data: UpdateBusinessTypeVersionDraftData): Promise<BusinessTypeVersionMutationResult>;
122
+ publishBusinessTypeVersion(versionId: string, publishMeta: PublishBusinessTypeVersionData): Promise<BusinessTypeVersionMutationResult>;
123
+ /**
124
+ * Loads the referenced BundleVersions and verifies that all exist
125
+ * and are published (live or superseded — both allowed, because
126
+ * BusinessTypeBundle holds the concrete version). Throws NotFound for
127
+ * missing ones, UnprocessableEntity for non-published ones.
128
+ */
129
+ private loadAndAssertBundles;
130
+ private runStrictCheck;
131
+ private gateOrPass;
132
+ }
133
+
134
+ declare class MarketingProjectionsService {
135
+ private readonly repo;
136
+ constructor(repo: MarketingProjectionRepository);
137
+ list(filter: MarketingProjectionFilter): Promise<MarketingProjectionRow[]>;
138
+ getById(id: string): Promise<MarketingProjectionRow>;
139
+ create(data: CreateMarketingProjectionData): Promise<MarketingProjectionRow>;
140
+ update(id: string, data: UpdateMarketingProjectionData): Promise<MarketingProjectionRow>;
141
+ delete(id: string): Promise<void>;
142
+ }
143
+
144
+ declare class CatalogEntriesService implements OnApplicationBootstrap {
145
+ private readonly repo;
146
+ private readonly snapshot;
147
+ private readonly scanner;
148
+ private readonly featureUiRegistry;
149
+ private readonly logger;
150
+ private readonly autoSyncAtBoot;
151
+ constructor(repo: CatalogEntryRepository, snapshot?: DiscoverySnapshot | null, config?: CatalogServiceConfig, scanner?: DiscoveryScanner | null, featureUiRegistry?: FeatureUiRegistry | null);
152
+ /**
153
+ * Mirrors the discovery snapshot into the catalog entries at boot
154
+ * (discovery-as-SSOT, #12). Runs only when a snapshot is available and
155
+ * the kill switch `autoSyncDiscoveryAtBoot` is not set to `false`. A
156
+ * sync error deliberately does not abort the boot — the manual endpoint
157
+ * `POST /admin/catalog/discovery/sync` remains as a fallback.
158
+ */
159
+ onApplicationBootstrap(): Promise<void>;
160
+ listCapabilities(projectKey: string, codeStatus?: string): Promise<CapabilityCatalogEntryRow[]>;
161
+ listFeatures(projectKey: string, discoveryStatus?: string): Promise<FeatureCatalogEntryRow[]>;
162
+ listQuotas(projectKey: string, discoveryStatus?: string): Promise<QuotaCatalogEntryRow[]>;
163
+ /**
164
+ * Approval transition of a feature (#20). Validates the state machine;
165
+ * `approved` persists the approval signature from the current
166
+ * snapshot, `pending` (revoke/reactivate) deletes it, `outdated`/
167
+ * `obsolete` keep the last approval as history.
168
+ */
169
+ reviewFeature(projectKey: string, featureKey: string, data: ReviewCatalogEntryData, reviewedBy: string | null): Promise<FeatureCatalogEntryRow>;
170
+ /** Approval transition of a quota — same state machine as features (#20). */
171
+ reviewQuota(projectKey: string, quotaKey: string, data: ReviewCatalogEntryData, reviewedBy: string | null): Promise<QuotaCatalogEntryRow>;
172
+ private resolveReviewUpdate;
173
+ setFeatureI18n(projectKey: string, featureKey: string, i18n: CatalogEntryI18n): Promise<FeatureCatalogEntryRow>;
174
+ setQuotaI18n(projectKey: string, quotaKey: string, i18n: CatalogEntryI18n): Promise<QuotaCatalogEntryRow>;
175
+ /** Sets the editable default-locale label/description of a feature. */
176
+ setFeatureBase(projectKey: string, featureKey: string, data: UpdateCatalogEntryBaseData): Promise<FeatureCatalogEntryRow>;
177
+ /** Sets the editable default-locale label/description of a quota. */
178
+ setQuotaBase(projectKey: string, quotaKey: string, data: UpdateCatalogEntryBaseData): Promise<QuotaCatalogEntryRow>;
179
+ /**
180
+ * Synchronizes the catalog entries from a discovery snapshot.
181
+ * Idempotent — can run against the same snapshot any number of times.
182
+ */
183
+ syncFromSnapshot(snapshot: DiscoverySnapshot): Promise<SyncDiscoveryResult>;
184
+ /**
185
+ * `replaced` semantics (#39): if a key disappears from the snapshot AND
186
+ * another snapshot key claims it via `replaces`, the old entry gets
187
+ * the successor pointer — `obsolete` (from `retireMissing`)
188
+ * + `successorKey` instead of a bare `obsolete`. If a key reappears in
189
+ * the code, an existing pointer is deleted. A missing key
190
+ * without a claimant remains untouched (deleted without replacement).
191
+ */
192
+ private applySuccessorPointers;
193
+ }
194
+
195
+ declare function buildCatalogEntriesController(guards: Array<Type<CanActivate>>): Type;
196
+
197
+ declare class PromotionsService {
198
+ private readonly repo;
199
+ constructor(repo: PromotionRepository);
200
+ list(projectKey: string): Promise<PromotionRow[]>;
201
+ getById(id: string): Promise<PromotionRow>;
202
+ create(data: CreatePromotionData): Promise<PromotionRow>;
203
+ update(id: string, data: UpdatePromotionData): Promise<PromotionRow>;
204
+ delete(id: string): Promise<void>;
205
+ }
206
+
207
+ declare function buildPromotionsController(guards: Array<Type<CanActivate>>): Type;
208
+
209
+ declare class MarketingSettingsService {
210
+ private readonly repo;
211
+ constructor(repo: MarketingSettingsRepository);
212
+ /** Returns the stored config or `null` (in which case the full pool applies). */
213
+ get(projectKey: string): Promise<MarketingSettingsRow | null>;
214
+ upsert(projectKey: string, data: UpdateMarketingSettingsData): Promise<MarketingSettingsRow>;
215
+ }
216
+
217
+ declare function buildMarketingSettingsController(guards: Array<Type<CanActivate>>): Type;
218
+
219
+ declare class PublicMarketingCatalogService {
220
+ private readonly planRepo;
221
+ private readonly marketingRepo;
222
+ private readonly promotionRepo;
223
+ private readonly catalogEntryRepo;
224
+ private readonly bundleRepo;
225
+ constructor(planRepo: PlanRepository, marketingRepo: MarketingProjectionRepository, promotionRepo: PromotionRepository, catalogEntryRepo?: CatalogEntryRepository | null, bundleRepo?: BundleRepository | null);
226
+ getCatalog(projectKey: string, locale: string, currency: string, vatRate: number, asOf?: Date): Promise<PublicMarketingCatalogResponse>;
227
+ /**
228
+ * Loads all published-and-live bundle versions for a project and
229
+ * filters on `marketed === true`. Without a registered
230
+ * `BundleRepository` (apps without the SuperAdmin bundle editor) the
231
+ * list is empty — the public catalog stays plan-only.
232
+ */
233
+ private loadMarketedBundles;
234
+ /**
235
+ * Maps BundleVersionRow → PublicMarketingBundle. Locale resolution
236
+ * runs via `MarketingProjectionRepository.findByTarget('BUNDLE',
237
+ * bundleVersionId, locale)` — on a miss it falls back to
238
+ * `DEFAULT_LOCALE`. If the fallback is missing too, label/description
239
+ * come directly from the bundle root (denormalized in `BundleVersionRow`).
240
+ *
241
+ * If the marketing projection is `visible === false`, the bundle is
242
+ * hidden from the public response (preparation phase).
243
+ */
244
+ private toPublicBundle;
245
+ /**
246
+ * Labels for bundle features ∪ their requires (#48): `comparison.features`
247
+ * only covers the plan-feature union — bundle-only features would
248
+ * otherwise get no label in the public payload. Curated keys only;
249
+ * without a CatalogEntryRepository the map stays empty (frontends fall
250
+ * back to the key).
251
+ */
252
+ private resolveBundleFeatureLabels;
253
+ /** Loads the translated feature/quota labels (+ sortOrder) for a locale. */
254
+ private loadLabelMeta;
255
+ /**
256
+ * Resolves the displayed label of a top feature: the `label` override
257
+ * wins; otherwise the translated label of the referenced `key`.
258
+ */
259
+ private resolveTopFeature;
260
+ /** Row definitions of the comparison matrix (feature/quota union + labels). */
261
+ private buildComparison;
262
+ private resolvePromo;
263
+ }
264
+
265
+ declare function buildPublicMarketingCatalogController(guards: Array<Type<CanActivate>>, projectKey: string, currency: string, vatRate: number): Type;
266
+
267
+ declare class PlansService {
268
+ private readonly repo;
269
+ private readonly subscriptions;
270
+ constructor(repo: PlanRepository, subscriptions?: SubscriptionRepository | null);
271
+ /**
272
+ * Platform-wide count of active (ACTIVE/TRIAL) subscriptions per `planKey`,
273
+ * across versions and across all tenants (RLS-exempt). Plans without a
274
+ * subscription are absent from the map. Returns `{}` when no
275
+ * `SubscriptionRepository` is registered or it does not implement
276
+ * `countActiveByPlanKey`.
277
+ */
278
+ getTenantCounts(projectKey: string): Promise<Record<string, number>>;
279
+ listPlans(projectKey: string, opts?: {
280
+ onlyPublished?: boolean;
281
+ }): Promise<PlanRow[]>;
282
+ getPlan(planId: string): Promise<PlanRow>;
283
+ createPlan(data: CreatePlanData): Promise<PlanRow>;
284
+ updatePlan(planId: string, data: UpdatePlanData): Promise<PlanRow>;
285
+ softDeletePlan(planId: string): Promise<void>;
286
+ /**
287
+ * Hard-deletes the plan master record. Only allowed for plans without
288
+ * `PlanVersion` entries — published versions are immutable under
289
+ * contract protection P1 (existing subscriptions reference them). The
290
+ * caller must discard drafts beforehand via `DELETE plan-versions/:id`.
291
+ *
292
+ * If the repository does not implement `hardDelete`, the endpoint
293
+ * responds with 422 `PLAN_HARD_DELETE_NOT_IMPLEMENTED` — consumers can
294
+ * then fall back to `softDelete`.
295
+ */
296
+ hardDeletePlan(planId: string): Promise<void>;
297
+ /**
298
+ * Platform invariant (contract protection P1): once a plan has ever had a
299
+ * published version, its plan master record is retained — regardless of
300
+ * whether the version has since been superseded. Subscriptions still
301
+ * reference it via the version, and the audit log must be able to resolve
302
+ * the plan.
303
+ */
304
+ private assertNoPublishedVersions;
305
+ private assertNoDrafts;
306
+ }
307
+
308
+ declare class PlanVersionsService {
309
+ private readonly repo;
310
+ private readonly snapshot;
311
+ private readonly subscriptions;
312
+ private readonly scanner;
313
+ private readonly catalogEntries;
314
+ private readonly logger;
315
+ private mode;
316
+ private readonly marketedOnly;
317
+ constructor(repo: PlanRepository, snapshot?: DiscoverySnapshot | null, config?: CatalogServiceConfig, subscriptions?: SubscriptionRepository | null, scanner?: DiscoveryScanner | null, catalogEntries?: CatalogEntryRepository | null);
318
+ /**
319
+ * Discards a draft version. Allowed only while the version has not yet
320
+ * been published — published versions are preserved immutably,
321
+ * because existing subscriptions reference them (contract protection P1).
322
+ *
323
+ * If the repository does not implement the method, the endpoint
324
+ * responds with 501 Not Implemented (consumers without the editor stack
325
+ * should not register `PlanVersionsService` anyway).
326
+ */
327
+ discardPlanDraft(versionId: string): Promise<void>;
328
+ /**
329
+ * @param planUuid UUID of the plan root (Plan.id) — resolved to planKey
330
+ * before the repo call (lifecycle methods work with planKey because
331
+ * PlanVersion.planId is a string in the schema).
332
+ */
333
+ listPlanVersions(planUuid: string): Promise<PlanVersionRow[]>;
334
+ getPlanVersion(versionId: string): Promise<PlanVersionRow>;
335
+ /**
336
+ * Annotates each version with `isLatestInChain` (highest version number
337
+ * in the root) + `subscriptionCount` so that UI and backend make the same
338
+ * editability decision. `subscriptionCount` stays
339
+ * `undefined` when no SubscriptionRepository is registered —
340
+ * `isVersionEditable` interprets that fail-closed (= frozen).
341
+ */
342
+ private annotateEditability;
343
+ /**
344
+ * @param data.planId — the plan UUID from the controller. Resolved to
345
+ * planKey before the repo call.
346
+ */
347
+ createPlanDraft(data: CreatePlanVersionDraftData): Promise<PlanVersionMutationResult>;
348
+ updatePlanDraft(versionId: string, data: UpdatePlanVersionDraftData): Promise<PlanVersionMutationResult>;
349
+ publishPlanVersion(versionId: string, publishMeta: PublishPlanVersionData): Promise<PlanVersionMutationResult>;
350
+ /**
351
+ * Terminates a **live** PlanVersion by setting `endsAt`.
352
+ * Unlike auto-succession (validUntil), this is an explicit
353
+ * user action: the version is not replaced by a successor version
354
+ * but expires on the chosen date. Existing subscriptions
355
+ * (P1) stay bound — this only affects new bookings.
356
+ *
357
+ * Idempotent — a second call with a different date overwrites.
358
+ *
359
+ * Preconditions:
360
+ * - Version must be live (`publishedAt != null && supersededAt == null`).
361
+ * - `endsAt` must lie in the future (`endsAt > now`).
362
+ *
363
+ * If the repository does not implement the method, the endpoint
364
+ * responds with 422 `PLAN_TERMINATE_NOT_IMPLEMENTED`.
365
+ */
366
+ terminatePlanVersion(versionId: string, endsAt: Date): Promise<PlanVersionRow>;
367
+ private runStrictCheck;
368
+ private gateOrPass;
369
+ }
370
+
371
+ /**
372
+ * Builds the controller class at boot time with the guards configured by the
373
+ * consumer. Pattern analogous to `buildDiscoveryController`.
374
+ */
375
+ declare function buildBundlesController(guards: Array<Type<CanActivate>>): Type;
376
+ /**
377
+ * Second controller with the version-specific endpoints. Deliberately
378
+ * separate, because they do not go through `bundles/:id/versions/:vid` but
379
+ * through stable version IDs (`/admin/catalog/bundle-versions/:id`) — this
380
+ * way the UI can work with a version-ID reference without a bundle lookup.
381
+ */
382
+ declare function buildBundleVersionsController(guards: Array<Type<CanActivate>>): Type;
383
+
384
+ declare function buildBusinessTypesController(guards: Array<Type<CanActivate>>): Type;
385
+ declare function buildBusinessTypeVersionsController(guards: Array<Type<CanActivate>>): Type;
386
+
387
+ declare function buildMarketingProjectionsController(guards: Array<Type<CanActivate>>): Type;
388
+
389
+ declare function buildPlansController(guards: Array<Type<CanActivate>>): Type;
390
+ /**
391
+ * Version-specific endpoints. Stable by version ID — the UI does not need
392
+ * to know the plan master.
393
+ */
394
+ declare function buildPlanVersionsController(guards: Array<Type<CanActivate>>): Type;
395
+
396
+ interface CatalogControllerConfig {
397
+ /**
398
+ * Class-level guards for `BundlesController` and `BundleVersionsController`.
399
+ * REQUIRED — otherwise `forRoot()` throws at boot. Pass `[]` explicitly
400
+ * if the endpoints should intentionally be auth-free.
401
+ */
402
+ guards: Array<Type<CanActivate>>;
403
+ }
404
+ interface CatalogModuleOptions {
405
+ /** Adapter for `bundles` + `bundle_versions` persistence. */
406
+ bundleRepository: ProviderSpec<BundleRepository>;
407
+ /**
408
+ * Adapter for `business_types` + `business_type_versions` +
409
+ * `business_type_bundles` persistence. Optional — if omitted,
410
+ * BusinessTypesService + controller are not registered.
411
+ */
412
+ businessTypeRepository?: ProviderSpec<BusinessTypeRepository>;
413
+ /**
414
+ * Adapter for `marketing_projections` persistence. Optional — if
415
+ * omitted, MarketingProjectionsService + controller are not
416
+ * registered.
417
+ */
418
+ marketingProjectionRepository?: ProviderSpec<MarketingProjectionRepository>;
419
+ /**
420
+ * Adapter for `capability_/feature_/quota_catalog_entries` persistence
421
+ * (discovery review, SPEC_V2 §6.3). Optional — if omitted,
422
+ * CatalogEntriesService + controller are not registered.
423
+ */
424
+ catalogEntryRepository?: ProviderSpec<CatalogEntryRepository>;
425
+ /**
426
+ * Adapter for `promotions` persistence (SPEC_V2 §9a). Optional — if
427
+ * omitted, PromotionsService + controller are not registered.
428
+ */
429
+ promotionRepository?: ProviderSpec<PromotionRepository>;
430
+ /**
431
+ * Adapter for `marketing_settings` persistence (SPEC_V2 §6.5 —
432
+ * `activeLocales`). Optional.
433
+ */
434
+ marketingSettingsRepository?: ProviderSpec<MarketingSettingsRepository>;
435
+ /**
436
+ * Auth-free pricing-page endpoint `GET /public/marketing-catalog`
437
+ * (SPEC_V2 §9). Registered only when plan-, marketingProjection-
438
+ * and promotionRepository are set. `guards` is usually `[]`.
439
+ */
440
+ publicMarketingCatalog?: {
441
+ guards: Array<Type<CanActivate>>;
442
+ projectKey: string;
443
+ currency: string;
444
+ vatRate: number;
445
+ };
446
+ /**
447
+ * SPEC_V2 §11.1 M6 (Pack 1) — adapter for `plans` master-record persistence.
448
+ * Optional; if omitted, PlansService + controller are not
449
+ * registered (apps without a need for the plan editor stay unchanged).
450
+ */
451
+ planRepository?: ProviderSpec<PlanRepository>;
452
+ /**
453
+ * Controller mount for `/admin/catalog/bundles` + `/admin/catalog/bundle-versions`
454
+ * + (if businessTypeRepository is set) `/admin/catalog/business-types` +
455
+ * `/admin/catalog/business-type-versions`. If omitted, the
456
+ * endpoints are not registered — but the services remain usable via DI.
457
+ */
458
+ controller?: CatalogControllerConfig;
459
+ /**
460
+ * Strict-mode mode. `blocking` (default, #12) throws HTTP 422 on
461
+ * violations and requires a DiscoverySnapshot; `warn-only` returns them
462
+ * only as a warning (transition until 100% discovery coverage).
463
+ */
464
+ strictModeCheckMode?: 'warn-only' | 'blocking';
465
+ /**
466
+ * Discovery→catalog auto-sync at boot (#12). Default `true` — as soon as a
467
+ * `catalogEntryRepository` is wired and a DiscoverySnapshot is available,
468
+ * `CatalogEntriesService` mirrors the discovery into the DB at startup. `false`
469
+ * disables the boot sync; the manual sync endpoint remains usable.
470
+ */
471
+ autoSyncDiscoveryAtBoot?: boolean;
472
+ /**
473
+ * Feature keys the catalog is deliberately allowed to carry without them
474
+ * existing in the discovery snapshot (marketed non-code features such as
475
+ * support SLAs, e.g. `['PRIORITY_SUPPORT']`). The strict-mode check excludes
476
+ * them from BUNDLE_/PLAN_FEATURE_UNKNOWN. Use sparingly.
477
+ */
478
+ marketedOnlyFeatures?: string[];
479
+ /**
480
+ * Optional consumer-curated FeatureUiRegistry (label/description/icon
481
+ * per feature — the same one passed to `PublicCatalogModule.forRoot`). The
482
+ * discovery auto-sync seeds EMPTY `FeatureCatalogEntry` fields
483
+ * (label/description/icon) from it at boot; SuperAdmin edits stay untouched. This
484
+ * makes `FeatureCatalogEntry` the SSOT for UI metadata (SPEC_V2 §6.3 / #12).
485
+ */
486
+ featureUiRegistry?: FeatureUiRegistry;
487
+ /**
488
+ * Modules whose providers must be visible in the DI scope of this
489
+ * DynamicModule — typically: `AuthModule`/`PlatformAdminModule`, because the
490
+ * controller guards come from there.
491
+ */
492
+ imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
493
+ /** Additional providers registered in the DynamicModule itself. */
494
+ extraProviders?: Provider[];
495
+ /** Register the module globally — default `false`. */
496
+ global?: boolean;
497
+ }
498
+ declare class CatalogModule {
499
+ static forRoot(options: CatalogModuleOptions): DynamicModule;
500
+ }
501
+
502
+ /** Repository for `plans` (SPEC_V2 §11.1 M6 — Plan master CRUD). */
503
+ declare const PLAN_REPOSITORY_TOKEN: unique symbol;
504
+ /** Repository for `bundles` + `bundle_versions`. */
505
+ declare const BUNDLE_REPOSITORY_TOKEN: unique symbol;
506
+ /** Repository for `business_types` + `business_type_versions` + `business_type_bundles`. */
507
+ declare const BUSINESS_TYPE_REPOSITORY_TOKEN: unique symbol;
508
+ /** Repository for `marketing_projections`. */
509
+ declare const MARKETING_PROJECTION_REPOSITORY_TOKEN: unique symbol;
510
+ /**
511
+ * Repository for `capability_catalog_entries`, `feature_catalog_entries`
512
+ * and `quota_catalog_entries` (Discovery review, SPEC_V2 §6.3).
513
+ */
514
+ declare const CATALOG_ENTRY_REPOSITORY_TOKEN: unique symbol;
515
+ /** Repository for `promotions` (time-scheduled price campaigns, SPEC_V2 §9a). */
516
+ declare const PROMOTION_REPOSITORY_TOKEN: unique symbol;
517
+ /** Repository for `marketing_settings` (activeLocales, SPEC_V2 §6.5). */
518
+ declare const MARKETING_SETTINGS_REPOSITORY_TOKEN: unique symbol;
519
+ /**
520
+ * Service configuration: current app identity, strict mode.
521
+ * Provided by CatalogModule.forRoot().
522
+ */
523
+ declare const CATALOG_SERVICE_CONFIG_TOKEN: unique symbol;
524
+
525
+ /**
526
+ * Advisory warnings (#35): they inform but never block — not even in
527
+ * `strictModeCheckMode: 'blocking'`, Seed-Gate, or Preflight. Reason: a
528
+ * `requires` feature may be satisfied outside the checked draft
529
+ * (e.g. the Plan covers a Bundle's dependency), which the draft check
530
+ * cannot know without booking context.
531
+ */
532
+ declare const ADVISORY_STRICT_MODE_CODES: ReadonlySet<StrictModeWarningCode>;
533
+ /** Returns only the warnings that are allowed to gate in blocking paths. */
534
+ declare function blockingStrictModeWarnings(warnings: StrictModeWarning[]): StrictModeWarning[];
535
+ /**
536
+ * Validates a Bundle draft definition against the current discovery snapshot.
537
+ * Returns a (possibly empty) list of warnings.
538
+ *
539
+ * Checked:
540
+ * - **Feature existence** (`BUNDLE_FEATURE_UNKNOWN`): every `features[i]`
541
+ * must exist as a `DiscoveredFeature` in the snapshot. Code is the
542
+ * source of truth for features (via @ImplementsCapability). Keys in
543
+ * `marketedOnlyFeatures` are deliberately exempt — marketed
544
+ * non-code features (e.g. support SLAs) that never have a Capability.
545
+ * - **Quota existence** (`QUOTA_MISSING`): every `quotas` key must exist
546
+ * as a `DiscoveredQuota` in the snapshot (provided the app uses quotas
547
+ * at all — empty quotas are allowed).
548
+ * - **Plan key existence** (`BUNDLE_PLAN_KEY_UNKNOWN`, optional via
549
+ * `knownPlanKeys`): every `compatibility.planIds[i]` must be an
550
+ * existing PlanKey in the current Project. Called by the
551
+ * BundlesService once the plan list is available;
552
+ * `knownPlanKeys = null` skips the check (e.g. when the PlanRepository
553
+ * is not registered).
554
+ * - **Approved gate** (`BUNDLE_FEATURE_NOT_APPROVED`/`QUOTA_NOT_APPROVED`,
555
+ * optional via `approved`, #20 Slice 5): only approved features/quotas
556
+ * (`discoveryStatus = 'approved'`) are sellable. `approved = null`
557
+ * skips the approval part; marketed-only features are exempt.
558
+ * - **Feature dependencies** (`BUNDLE_FEATURE_DEPENDENCY_UNSATISFIED`, #35,
559
+ * advisory): `requires` features that are not in the Bundle itself.
560
+ *
561
+ * **Not** checked (belongs in other validations):
562
+ * - Pricing (purely business, no discovery relation)
563
+ * - Compatibility.businessTypeKeys whitelists (BUNDLE_COMPATIBILITY code,
564
+ * only checked at BusinessType publish)
565
+ * - Disjointness (BUNDLE_DISJOINTNESS, also a BusinessType concern)
566
+ */
567
+ declare function validateBundleDraft(draft: {
568
+ features: string[];
569
+ quotas: Record<string, number>;
570
+ compatibility?: {
571
+ planIds?: string[];
572
+ businessTypeKeys?: string[];
573
+ };
574
+ }, snapshot: DiscoverySnapshot, knownPlanKeys?: Set<string> | null, marketedOnlyFeatures?: ReadonlySet<string>, approved?: ApprovedCatalogKeys | null): StrictModeWarning[];
575
+ /**
576
+ * Validates a BusinessType draft definition against the discovery snapshot
577
+ * and the referenced BundleVersions. Three violations:
578
+ *
579
+ * - **BUNDLE_DISJOINTNESS** (GESCHAEFTSTYP_SPEC §6.3): two Bundles in
580
+ * the composition activate the same feature. Hard block at publish.
581
+ * - **BUNDLE_COMPATIBILITY** (GESCHAEFTSTYP_SPEC §6.4): a Bundle has set
582
+ * a `compatibility.businessTypeKeys` whitelist, but the target
583
+ * businessTypeKey is not included. Soft warning (no
584
+ * hard block, but an audit marker on override).
585
+ * - **QUOTA_MISSING**: QuotaOverride keys that do not exist in discovery
586
+ * (analogous to the Bundle strict check).
587
+ *
588
+ * Bundle existence and published status are checked in the service before
589
+ * this check (NotFoundException on a missing version, no warning).
590
+ */
591
+ declare function validateBusinessTypeDraft(draft: {
592
+ businessTypeKey: string;
593
+ quotaOverrides: Record<string, number>;
594
+ }, bundles: BundleVersionRow[], snapshot: DiscoverySnapshot, approved?: ApprovedCatalogKeys | null): StrictModeWarning[];
595
+ /**
596
+ * Validates a PlanVersion draft definition against the discovery snapshot
597
+ * (SPEC_V2 §8.2 Pack 3a). Returns a (possibly empty) list of
598
+ * warnings.
599
+ *
600
+ * Checked:
601
+ * - **Feature existence** (`PLAN_FEATURE_UNKNOWN`): every `features[i]`
602
+ * must exist as a `DiscoveredFeature` in the snapshot. Code is the
603
+ * source of truth for features (via @ImplementsCapability). Keys in
604
+ * `marketedOnlyFeatures` are deliberately exempt — marketed
605
+ * non-code features (e.g. support SLAs) that never have a Capability.
606
+ * - **Quota existence** (`QUOTA_MISSING`): every `quotas` key must exist
607
+ * as a `DiscoveredQuota` (via `@DefinesQuota`) in the snapshot.
608
+ * - **Approved gate** (`PLAN_FEATURE_NOT_APPROVED`/`QUOTA_NOT_APPROVED`,
609
+ * optional via `approved`, #20 Slice 5): only approved features/quotas
610
+ * are sellable. `approved = null` skips the approval part.
611
+ * - **Feature dependencies** (`PLAN_FEATURE_DEPENDENCY_UNSATISFIED`, #35,
612
+ * advisory): `requires` features that are not in the Plan itself —
613
+ * may be covered at the Tenant by booked Bundles.
614
+ *
615
+ * **Not** checked (belongs in other validations):
616
+ * - Pricing (purely business)
617
+ * - Marketing fields (separately in §8.2 item 6)
618
+ */
619
+ declare function validatePlanDraft(draft: {
620
+ features: string[];
621
+ quotas: Record<string, number>;
622
+ }, snapshot: DiscoverySnapshot, marketedOnlyFeatures?: ReadonlySet<string>, approved?: ApprovedCatalogKeys | null): StrictModeWarning[];
623
+
624
+ /**
625
+ * Signature of a feature's capability set: `capabilityKey@codeStatus`,
626
+ * sorted and `|`-separated. `internal` capabilities do not count (they also
627
+ * do not appear in the catalog).
628
+ *
629
+ * `requires`/`replaces` (#35/#39) are only included when they are set —
630
+ * this keeps existing signatures without that metadata byte-identical;
631
+ * otherwise the platform upgrade would flip every approved entry to
632
+ * `outdated` even though nothing in the code changed.
633
+ */
634
+ declare function featureApprovalSignature(featureKey: string, snapshot: DiscoverySnapshot): string;
635
+ /**
636
+ * Signature of the code-derived quota facts:
637
+ * `unit|enforcementMode|usageProvider|featureKey`. `enforcementMode` is the
638
+ * catalog view of the policy (`hardCap` → `hard`, otherwise `soft`) —
639
+ * identical to the derivation in the sync. `replaces` (#39) is only appended
640
+ * when set (same stability rationale as `featureApprovalSignature`).
641
+ */
642
+ declare function quotaApprovalSignature(quota: DiscoveredQuota): string;
643
+
644
+ declare function loadApprovedCatalogKeys(repo: CatalogEntryRepository | null, projectKey: string): Promise<ApprovedCatalogKeys | null>;
645
+
646
+ interface PreflightInput {
647
+ snapshot: DiscoverySnapshot;
648
+ /** All live PlanVersions (publishedAt != null AND supersededAt = null). */
649
+ planVersions: PlanVersionRow[];
650
+ /** All live BundleVersions. */
651
+ bundleVersions: BundleVersionRow[];
652
+ /** All live BusinessTypeVersions. */
653
+ businessTypeVersions: BusinessTypeVersionRow[];
654
+ /**
655
+ * Approved gate (#20 Slice 5): approved feature/quota keys from the
656
+ * catalog entries (`loadApprovedCatalogKeys`). Omitted/`null` →
657
+ * existence checks only.
658
+ */
659
+ approved?: ApprovedCatalogKeys | null;
660
+ }
661
+ interface PreflightFinding {
662
+ /** `plan` / `bundle` / `business-type`. */
663
+ kind: 'plan' | 'bundle' | 'business-type';
664
+ /** ID of the specific version the violation is attached to. */
665
+ versionId: string;
666
+ /** Human-readable key for the UI: `STARTER` or `BANKING` or `SPORT_VEREIN`. */
667
+ entityKey: string;
668
+ /** Version number. */
669
+ version: number;
670
+ warning: StrictModeWarning;
671
+ }
672
+ interface PreflightReport {
673
+ /** `'ok'` if no findings; otherwise `'error'`. */
674
+ overall: 'ok' | 'error';
675
+ /** Aggregated statistics: how many violations per kind. */
676
+ counts: {
677
+ planFindings: number;
678
+ bundleFindings: number;
679
+ businessTypeFindings: number;
680
+ total: number;
681
+ };
682
+ findings: PreflightFinding[];
683
+ }
684
+ /**
685
+ * Runs all three strict checks against the discovery snapshot.
686
+ * Collects findings by entity type and returns a sorted report
687
+ * (deterministic for test stability).
688
+ */
689
+ declare function runPreflight(input: PreflightInput): PreflightReport;
690
+ /**
691
+ * Formats the report output for CLI stdout. Returns a multi-line string.
692
+ */
693
+ declare function formatPreflightReport(report: PreflightReport): string;
694
+ /** Exit code for the CLI wrapping: 0 on OK, 4 on violations. */
695
+ declare function preflightExitCode(report: PreflightReport): number;
696
+
697
+ interface SeedPlanDraft {
698
+ planKey: string;
699
+ features: string[];
700
+ quotas?: Record<string, number>;
701
+ }
702
+ interface SeedBundleDraft {
703
+ bundleKey: string;
704
+ features: string[];
705
+ quotas?: Record<string, number>;
706
+ }
707
+ interface SeedGateInput {
708
+ snapshot: DiscoverySnapshot;
709
+ /** Plan drafts to seed (before persisting). */
710
+ plans?: SeedPlanDraft[];
711
+ /** Bundle drafts to seed (before persisting). */
712
+ bundles?: SeedBundleDraft[];
713
+ /**
714
+ * Approved gate (#20 Slice 5): approved feature/quota keys from the
715
+ * catalog entries (`loadApprovedCatalogKeys`). Omitted/`null` →
716
+ * existence checks only (e.g. first seed, before any catalog entries exist).
717
+ */
718
+ approved?: ApprovedCatalogKeys | null;
719
+ }
720
+ interface SeedGateFinding {
721
+ kind: 'plan' | 'bundle';
722
+ /** Human-readable key: `STARTER` or `BANKING`. */
723
+ entityKey: string;
724
+ warning: StrictModeWarning;
725
+ }
726
+ interface SeedGateReport {
727
+ /** `'ok'` if no findings; otherwise `'error'`. */
728
+ overall: 'ok' | 'error';
729
+ counts: {
730
+ planFindings: number;
731
+ bundleFindings: number;
732
+ total: number;
733
+ };
734
+ findings: SeedGateFinding[];
735
+ }
736
+ /**
737
+ * Validates seeded plan/bundle drafts against the DiscoverySnapshot.
738
+ * Collects findings and returns a deterministically sorted report.
739
+ */
740
+ declare function validateSeedAgainstSnapshot(input: SeedGateInput): SeedGateReport;
741
+ /** Formats the report output for CLI/seed stdout. Returns a multi-line string. */
742
+ declare function formatSeedGateReport(report: SeedGateReport): string;
743
+ /** Exit code for CLI/seed wrapping: 0 on OK, 4 on violations (analogous to Preflight). */
744
+ declare function seedGateExitCode(report: SeedGateReport): number;
745
+
746
+ type SeedGateMode = 'report-only' | 'blocking';
747
+ interface SeedGateRunOptions {
748
+ /** Path of the snapshot file persisted by the scanner. */
749
+ snapshotPath: string;
750
+ plans?: SeedPlanDraft[];
751
+ bundles?: SeedBundleDraft[];
752
+ /** Approved gate (#20 Slice 5) — `null`/omitted = existence checks only. */
753
+ approved?: ApprovedCatalogKeys | null;
754
+ /** Default `'report-only'`; consumers map their env variable onto this. */
755
+ mode?: SeedGateMode;
756
+ /** Log sinks — default `console.log`/`console.warn`/`console.error`. */
757
+ log?: (line: string) => void;
758
+ warn?: (line: string) => void;
759
+ error?: (line: string) => void;
760
+ /** Exit function in the blocking case — default `process.exit`. Injectable in tests. */
761
+ exit?: (code: number) => never;
762
+ }
763
+ /**
764
+ * Loads the snapshot, validates the seed drafts and prints the report.
765
+ * Returns the report — or `null` if the snapshot is missing and the mode
766
+ * is report-only (gate skipped). In blocking mode the function does not
767
+ * return on violations or a missing snapshot (exit 4).
768
+ */
769
+ declare function runSeedGateFromFile(options: SeedGateRunOptions): SeedGateReport | null;
770
+
771
+ declare class CreateBundleDto {
772
+ projectKey: string;
773
+ bundleKey: string;
774
+ label: string;
775
+ description?: string;
776
+ icon?: string;
777
+ sortOrder?: number;
778
+ /** Locale translations { "en": { label, description }, … }. */
779
+ i18n?: Record<string, {
780
+ label?: string;
781
+ description?: string;
782
+ }>;
783
+ }
784
+ declare class UpdateBundleDto {
785
+ label?: string;
786
+ description?: string | null;
787
+ icon?: string | null;
788
+ sortOrder?: number;
789
+ /** Locale translations { "en": { label, description }, … }. */
790
+ i18n?: Record<string, {
791
+ label?: string;
792
+ description?: string;
793
+ }>;
794
+ }
795
+ declare class CreateBundleVersionDraftDto {
796
+ features: string[];
797
+ quotas?: Record<string, number>;
798
+ compatibility?: {
799
+ businessTypeKeys?: string[];
800
+ planIds?: string[];
801
+ };
802
+ pricingOverrides?: Array<{
803
+ businessTypeKey?: string;
804
+ planId?: string;
805
+ monthlyNet?: string | null;
806
+ yearlyNet?: string | null;
807
+ }>;
808
+ monthlyNet?: string | null;
809
+ yearlyNet?: string | null;
810
+ marketed?: boolean;
811
+ changeNote?: string;
812
+ baseVersionId?: string | null;
813
+ validFrom?: string | null;
814
+ validUntil?: string | null;
815
+ }
816
+ declare class UpdateBundleVersionDraftDto {
817
+ features?: string[];
818
+ quotas?: Record<string, number>;
819
+ compatibility?: {
820
+ businessTypeKeys?: string[];
821
+ planIds?: string[];
822
+ };
823
+ pricingOverrides?: Array<{
824
+ businessTypeKey?: string;
825
+ planId?: string;
826
+ monthlyNet?: string | null;
827
+ yearlyNet?: string | null;
828
+ }>;
829
+ monthlyNet?: string | null;
830
+ yearlyNet?: string | null;
831
+ marketed?: boolean;
832
+ changeNote?: string;
833
+ validFrom?: string | null;
834
+ validUntil?: string | null;
835
+ }
836
+ declare class PublishBundleVersionDto {
837
+ forceRegressive?: boolean;
838
+ /**
839
+ * Allows a deliberately free bundle (explicit price 0.00) and lifts the
840
+ * zero-price gate (otherwise 422 BUNDLE_VERSION_ZERO_PRICE). Default: gate
841
+ * active (protection against seed placeholders).
842
+ */
843
+ allowZeroPrice?: boolean;
844
+ /**
845
+ * Required on publish if the draft carries no `validFrom`
846
+ * (SPEC_V2 §4.2 + §11.1 M6 Pack 2c, analogous to PublishPlanVersionDto).
847
+ * Service strictly checks > `validFrom` of the predecessor version.
848
+ */
849
+ validFrom?: string | null;
850
+ validUntil?: string | null;
851
+ }
852
+
853
+ declare class CreateBusinessTypeDto {
854
+ projectKey: string;
855
+ businessTypeKey: string;
856
+ label: string;
857
+ description?: string;
858
+ icon?: string;
859
+ sortOrder?: number;
860
+ }
861
+ declare class UpdateBusinessTypeDto {
862
+ label?: string;
863
+ description?: string | null;
864
+ icon?: string | null;
865
+ sortOrder?: number;
866
+ }
867
+ declare class BusinessTypeBundleInputDto {
868
+ bundleVersionId: string;
869
+ sortOrder?: number;
870
+ }
871
+ declare class CreateBusinessTypeVersionDraftDto {
872
+ bundles: BusinessTypeBundleInputDto[];
873
+ quotaOverrides?: Record<string, number>;
874
+ monthlyNet?: string | null;
875
+ yearlyNet?: string | null;
876
+ marketed?: boolean;
877
+ changeNote?: string;
878
+ baseVersionId?: string | null;
879
+ }
880
+ declare class UpdateBusinessTypeVersionDraftDto {
881
+ bundles?: BusinessTypeBundleInputDto[];
882
+ quotaOverrides?: Record<string, number>;
883
+ monthlyNet?: string | null;
884
+ yearlyNet?: string | null;
885
+ marketed?: boolean;
886
+ changeNote?: string;
887
+ }
888
+ declare class PublishBusinessTypeVersionDto {
889
+ forceRegressive?: boolean;
890
+ }
891
+
892
+ declare class MarketingTopFeatureDto {
893
+ /** Optional feature/quota key reference (auto-translation of the label). */
894
+ key?: string;
895
+ /** Free text or override; empty + `key` set = auto label. */
896
+ label: string;
897
+ /** Optional bold addendum — may be empty. */
898
+ strong: string;
899
+ }
900
+ declare class CreateMarketingProjectionDto {
901
+ projectKey: string;
902
+ targetType: 'PLAN' | 'BUNDLE' | 'BUSINESS_TYPE';
903
+ targetVersionId: string;
904
+ locale?: string;
905
+ displayLabel: string;
906
+ description: string;
907
+ visible?: boolean;
908
+ badge?: string;
909
+ topFeatures?: MarketingTopFeatureDto[];
910
+ trialEnabled?: boolean;
911
+ trialDays?: number;
912
+ priceTag?: string | null;
913
+ ctaLabel?: string | null;
914
+ priority?: number;
915
+ highlight?: boolean;
916
+ }
917
+ declare class UpdateMarketingProjectionDto {
918
+ displayLabel?: string;
919
+ description?: string;
920
+ visible?: boolean;
921
+ badge?: string;
922
+ topFeatures?: MarketingTopFeatureDto[];
923
+ trialEnabled?: boolean;
924
+ trialDays?: number;
925
+ priceTag?: string | null;
926
+ ctaLabel?: string | null;
927
+ priority?: number;
928
+ highlight?: boolean;
929
+ }
930
+ declare class ListMarketingProjectionsQueryDto {
931
+ projectKey: string;
932
+ targetType?: 'PLAN' | 'BUNDLE' | 'BUSINESS_TYPE';
933
+ targetVersionId?: string;
934
+ locale?: string;
935
+ }
936
+
937
+ declare class ListCatalogEntriesQueryDto {
938
+ projectKey: string;
939
+ /** Filter for features/quotas (approval lifecycle). */
940
+ discoveryStatus?: string;
941
+ /** Filter for capabilities (read-only code facts). */
942
+ codeStatus?: string;
943
+ }
944
+ /**
945
+ * Body of `PATCH …/{features,quotas}/:key/review` — target status of the
946
+ * approval state machine (#20). Allowed transitions are validated by the service.
947
+ */
948
+ declare class ReviewCatalogEntryDto {
949
+ discoveryStatus: DiscoveryStatus;
950
+ }
951
+ /** Body of `PATCH …/{features,quotas}/:key/i18n`. */
952
+ declare class UpdateCatalogEntryI18nDto {
953
+ i18n: CatalogEntryI18n;
954
+ }
955
+ /**
956
+ * Body of `POST …/discovery/sync` — the discovery snapshot the UI
957
+ * previously loaded from `GET /admin/discovery`. Validated by the service
958
+ * against `projectKey`; deep validation is not needed because the
959
+ * SuperAdmin guard secures the source.
960
+ */
961
+ declare class SyncDiscoveryDto {
962
+ snapshot: DiscoverySnapshot;
963
+ }
964
+
965
+ declare class ListPromotionsQueryDto {
966
+ projectKey: string;
967
+ }
968
+ declare class CreatePromotionDto {
969
+ projectKey: string;
970
+ internalLabel: string;
971
+ type: PromotionType;
972
+ /** Type-dependent: number | { price, months }. */
973
+ value: PromotionValue;
974
+ appliesTo?: string[];
975
+ targetType?: PromotionTargetType;
976
+ billingCycle?: PromotionBillingCycle;
977
+ validFrom: string;
978
+ validTo: string;
979
+ priority?: number;
980
+ onlyLocales?: string[] | null;
981
+ requiresCoupon?: boolean;
982
+ codes?: string[];
983
+ color?: string;
984
+ i18n?: PromotionI18n;
985
+ }
986
+ declare class UpdatePromotionDto {
987
+ internalLabel?: string;
988
+ type?: PromotionType;
989
+ value?: PromotionValue;
990
+ appliesTo?: string[];
991
+ targetType?: PromotionTargetType;
992
+ billingCycle?: PromotionBillingCycle;
993
+ validFrom?: string;
994
+ validTo?: string;
995
+ priority?: number;
996
+ onlyLocales?: string[] | null;
997
+ requiresCoupon?: boolean;
998
+ codes?: string[];
999
+ color?: string;
1000
+ i18n?: PromotionI18n;
1001
+ }
1002
+
1003
+ declare class ListMarketingSettingsQueryDto {
1004
+ projectKey: string;
1005
+ }
1006
+ /** Body of `PUT /admin/catalog/marketing-settings`. */
1007
+ declare class UpdateMarketingSettingsDto {
1008
+ projectKey: string;
1009
+ activeLocales: string[];
1010
+ }
1011
+
1012
+ declare class CreatePlanDto {
1013
+ projectKey: string;
1014
+ planKey: string;
1015
+ label: string;
1016
+ description?: string;
1017
+ icon?: string;
1018
+ sortOrder?: number;
1019
+ }
1020
+ declare class UpdatePlanDto {
1021
+ label?: string;
1022
+ description?: string | null;
1023
+ icon?: string | null;
1024
+ sortOrder?: number;
1025
+ }
1026
+
1027
+ declare class CreatePlanVersionDraftDto {
1028
+ features: string[];
1029
+ /**
1030
+ * Persisted bundle selection (bundleKeys, SCREAMING_SNAKE_CASE).
1031
+ * Optional + defaults to empty — see `PlanVersionRow.bundles`.
1032
+ */
1033
+ bundles?: string[];
1034
+ quotas: Record<string, number>;
1035
+ monthlyNet: string;
1036
+ yearlyNet: string;
1037
+ marketed?: boolean;
1038
+ changeNote?: string;
1039
+ baseVersionId?: string | null;
1040
+ /** Optional in the draft (required at publish). ISO date string. SPEC_V2 §4.2. */
1041
+ validFrom?: string | null;
1042
+ /** Optional; null = unlimited. ISO date string. */
1043
+ validUntil?: string | null;
1044
+ }
1045
+ declare class UpdatePlanVersionDraftDto {
1046
+ features?: string[];
1047
+ /** Persisted bundle selection (bundleKeys). See `PlanVersionRow.bundles`. */
1048
+ bundles?: string[];
1049
+ quotas?: Record<string, number>;
1050
+ monthlyNet?: string;
1051
+ yearlyNet?: string;
1052
+ marketed?: boolean;
1053
+ changeNote?: string;
1054
+ validFrom?: string | null;
1055
+ validUntil?: string | null;
1056
+ }
1057
+ declare class PublishPlanVersionDto {
1058
+ forceRegressive?: boolean;
1059
+ /**
1060
+ * Deliberately allows free special contracts (price 0.00) and thereby lifts
1061
+ * the zero-price gate (otherwise 422 PLAN_VERSION_ZERO_PRICE). Default:
1062
+ * gate active (protection against seed placeholders).
1063
+ */
1064
+ allowZeroPrice?: boolean;
1065
+ /**
1066
+ * Required at publish (on the DTO or draft). The service strictly checks
1067
+ * `validFrom > predecessor.validFrom`. SPEC_V2 §4.2.
1068
+ */
1069
+ validFrom?: string | null;
1070
+ validUntil?: string | null;
1071
+ }
1072
+
1073
+ export { ADVISORY_STRICT_MODE_CODES, BUNDLE_REPOSITORY_TOKEN, BUSINESS_TYPE_REPOSITORY_TOKEN, BundlesService, BusinessTypeBundleInputDto, BusinessTypesService, CATALOG_ENTRY_REPOSITORY_TOKEN, CATALOG_SERVICE_CONFIG_TOKEN, type CatalogControllerConfig, CatalogEntriesService, CatalogModule, type CatalogModuleOptions, type CatalogServiceConfig, CreateBundleDto, CreateBundleVersionDraftDto, CreateBusinessTypeDto, CreateBusinessTypeVersionDraftDto, CreateMarketingProjectionDto, CreatePlanDto, CreatePlanVersionDraftDto, CreatePromotionDto, ListCatalogEntriesQueryDto, ListMarketingProjectionsQueryDto, ListMarketingSettingsQueryDto, ListPromotionsQueryDto, MARKETING_PROJECTION_REPOSITORY_TOKEN, MARKETING_SETTINGS_REPOSITORY_TOKEN, MarketingProjectionsService, MarketingSettingsService, PLAN_REPOSITORY_TOKEN, PROMOTION_REPOSITORY_TOKEN, PlanVersionsService, PlansService, type PreflightFinding, type PreflightInput, type PreflightReport, PromotionsService, PublicMarketingCatalogService, PublishBundleVersionDto, PublishBusinessTypeVersionDto, PublishPlanVersionDto, ReviewCatalogEntryDto, type SeedBundleDraft, type SeedGateFinding, type SeedGateInput, type SeedGateMode, type SeedGateReport, type SeedGateRunOptions, type SeedPlanDraft, SyncDiscoveryDto, UpdateBundleDto, UpdateBundleVersionDraftDto, UpdateBusinessTypeDto, UpdateBusinessTypeVersionDraftDto, UpdateCatalogEntryI18nDto, UpdateMarketingProjectionDto, UpdateMarketingSettingsDto, UpdatePlanDto, UpdatePlanVersionDraftDto, UpdatePromotionDto, blockingStrictModeWarnings, buildBundleVersionsController, buildBundlesController, buildBusinessTypeVersionsController, buildBusinessTypesController, buildCatalogEntriesController, buildMarketingProjectionsController, buildMarketingSettingsController, buildPlanVersionsController, buildPlansController, buildPromotionsController, buildPublicMarketingCatalogController, featureApprovalSignature, formatPreflightReport, formatSeedGateReport, loadApprovedCatalogKeys, preflightExitCode, quotaApprovalSignature, runPreflight, runSeedGateFromFile, seedGateExitCode, validateBundleDraft, validateBusinessTypeDraft, validatePlanDraft, validateSeedAgainstSnapshot };