@classytic/crm 0.9.0 → 1.1.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.
package/dist/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- import * as _$mongoose from "mongoose";
1
+ import { ModelBlueprint, PaginationConfig, PluginType, Repository } from "@classytic/mongokit";
2
2
  import { Connection, Model, Schema, SchemaDefinitionProperty } from "mongoose";
3
- import { PaginationConfig, PluginType, Repository } from "@classytic/mongokit";
4
3
  import { DomainEvent, EventHandler, EventMeta, EventTransport } from "@classytic/primitives/events";
5
- import * as _$_classytic_primitives_state_machine0 from "@classytic/primitives/state-machine";
4
+ import { InProcessEventBus } from "@classytic/primitives/event-infra";
5
+ import { ResolvedTenantConfig, TenantConfig } from "@classytic/repo-core/tenant";
6
6
  import { z } from "zod";
7
7
  import { StatusChangeEntry, StatusHistory, appendStatus, lastTransitionTo, latestEntry, timeInStatus } from "@classytic/primitives/status-history";
8
8
  import { OperationContext } from "@classytic/primitives/context";
9
9
  import { OutboxStore } from "@classytic/primitives/outbox";
10
-
10
+ import { HttpError } from "@classytic/repo-core/errors";
11
11
  //#region src/events/dispatch.d.ts
12
12
  /**
13
13
  * Context key for the post-commit event queue. `Symbol.for` so the host
@@ -59,12 +59,38 @@ declare class CrmEventDispatcher implements EventTransport {
59
59
  */
60
60
  declare function flushPending(transport: Pick<EventTransport, 'publish'> | undefined, events: DomainEvent[], logger?: CrmLogger): Promise<void>;
61
61
  //#endregion
62
+ //#region src/engine/modules.d.ts
63
+ /** Coarse CRM feature bundles. Keep entity-level switches out of the API. */
64
+ interface CrmModulesConfig {
65
+ /** Operational sales CRM: leads, accounts, contacts, opportunities and follow-up. */
66
+ sales?: boolean | undefined;
67
+ /** Customer communication: conversations, messages, consent and email events. */
68
+ engagement?: boolean | undefined;
69
+ /** Campaign and sequence automation. Implies engagement and sales. */
70
+ automation?: boolean | undefined;
71
+ }
72
+ interface ResolvedCrmModules {
73
+ readonly sales: boolean;
74
+ readonly engagement: boolean;
75
+ readonly automation: boolean;
76
+ }
77
+ declare const ALL_CRM_MODULES: ResolvedCrmModules;
78
+ /**
79
+ * Resolve module dependencies once at boot.
80
+ *
81
+ * Omitting `modules` preserves the pre-module package behaviour (everything
82
+ * enabled). Supplying it is opt-in: unspecified modules are disabled.
83
+ */
84
+ declare function resolveCrmModules(modules: CrmModulesConfig | undefined): ResolvedCrmModules;
85
+ //#endregion
62
86
  //#region src/models/tenant-field.d.ts
63
- type TenantFieldType = 'objectId' | 'string';
64
87
  interface SchemaBuildConfig {
65
- /** Schema type for the tenant column. Engine plumbs this everywhere. */
66
- tenantFieldType: TenantFieldType;
88
+ /** Canonical resolved tenant config. Engine plumbs this everywhere. */
89
+ tenant: ResolvedTenantConfig;
90
+ /** Optional Mongoose model namespace for hosts with generic model names. */
91
+ modelPrefix?: string | undefined;
67
92
  }
93
+ declare function crmModelName(config: SchemaBuildConfig, base: string): string;
68
94
  /**
69
95
  * Tenant-column schema definition. Use this anywhere a model has
70
96
  * `organizationId` — never write the raw type in a schema file.
@@ -74,7 +100,7 @@ declare function tenantFieldDef(config: SchemaBuildConfig): SchemaDefinitionProp
74
100
  //#region src/models/pipeline.schema.d.ts
75
101
  /** Mongoose document shape for a single embedded stage. */
76
102
  interface IStageDocument {
77
- _id: _$mongoose.Types.ObjectId;
103
+ _id: import('mongoose').Types.ObjectId;
78
104
  name: string;
79
105
  /** Ascending order in the pipeline. Treated as identifier on `moveToStage`. */
80
106
  sequence: number;
@@ -85,8 +111,8 @@ interface IStageDocument {
85
111
  }
86
112
  /** Mongoose document shape for a Pipeline. */
87
113
  interface IPipelineDocument {
88
- _id: _$mongoose.Types.ObjectId;
89
- organizationId: _$mongoose.Types.ObjectId | string;
114
+ _id: import('mongoose').Types.ObjectId;
115
+ organizationId: import('mongoose').Types.ObjectId | string;
90
116
  name: string;
91
117
  /** Deprecates the pipeline — existing opps still move, no new ones created. */
92
118
  isArchived: boolean;
@@ -138,8 +164,8 @@ interface ILeadEngagement {
138
164
  lastEngagedAt?: Date;
139
165
  }
140
166
  interface ILeadDocument {
141
- _id: _$mongoose.Types.ObjectId;
142
- organizationId: _$mongoose.Types.ObjectId | string;
167
+ _id: import('mongoose').Types.ObjectId;
168
+ organizationId: import('mongoose').Types.ObjectId | string;
143
169
  fullName: string;
144
170
  firstName?: string;
145
171
  lastName?: string;
@@ -155,11 +181,13 @@ interface ILeadDocument {
155
181
  statusHistory: ILeadStatusEntry[];
156
182
  ownerId?: string;
157
183
  tags: string[];
158
- convertedContactId?: _$mongoose.Types.ObjectId | string;
159
- convertedAccountId?: _$mongoose.Types.ObjectId | string;
160
- convertedOpportunityId?: _$mongoose.Types.ObjectId | string;
184
+ convertedContactId?: import('mongoose').Types.ObjectId | string;
185
+ convertedAccountId?: import('mongoose').Types.ObjectId | string;
186
+ convertedOpportunityId?: import('mongoose').Types.ObjectId | string;
161
187
  convertedAt?: Date;
162
188
  disqualifyReason?: string;
189
+ /** GDPR tombstone — set by `anonymize()` when PII was scrubbed. */
190
+ anonymizedAt?: Date;
163
191
  metadata?: Record<string, unknown>;
164
192
  createdAt: Date;
165
193
  updatedAt: Date;
@@ -189,12 +217,12 @@ interface IOpportunityStatusEntry {
189
217
  note?: string;
190
218
  }
191
219
  interface IOpportunityDocument {
192
- _id: _$mongoose.Types.ObjectId;
193
- organizationId: _$mongoose.Types.ObjectId | string;
220
+ _id: import('mongoose').Types.ObjectId;
221
+ organizationId: import('mongoose').Types.ObjectId | string;
194
222
  name: string;
195
- accountId?: _$mongoose.Types.ObjectId | string;
196
- primaryContactId?: _$mongoose.Types.ObjectId | string;
197
- pipelineId: _$mongoose.Types.ObjectId | string;
223
+ accountId?: import('mongoose').Types.ObjectId | string;
224
+ primaryContactId?: import('mongoose').Types.ObjectId | string;
225
+ pipelineId: import('mongoose').Types.ObjectId | string;
198
226
  stageId: string;
199
227
  status: OpportunityStatus;
200
228
  statusHistory: IOpportunityStatusEntry[];
@@ -203,9 +231,9 @@ interface IOpportunityDocument {
203
231
  probability: number;
204
232
  expectedCloseAt?: Date;
205
233
  closedAt?: Date;
206
- lostReasonId?: _$mongoose.Types.ObjectId | string;
234
+ lostReasonId?: import('mongoose').Types.ObjectId | string;
207
235
  ownerId?: string;
208
- sourceLeadId?: _$mongoose.Types.ObjectId | string;
236
+ sourceLeadId?: import('mongoose').Types.ObjectId | string;
209
237
  tags: string[];
210
238
  metadata?: Record<string, unknown>;
211
239
  createdAt: Date;
@@ -234,15 +262,17 @@ interface IContactInfoEmbedded {
234
262
  instagram?: string;
235
263
  }
236
264
  interface IContactDocument {
237
- _id: _$mongoose.Types.ObjectId;
238
- organizationId: _$mongoose.Types.ObjectId | string;
265
+ _id: import('mongoose').Types.ObjectId;
266
+ organizationId: import('mongoose').Types.ObjectId | string;
239
267
  name: IContactName;
240
268
  contact: IContactInfoEmbedded;
241
269
  jobTitle?: string;
242
- accountId?: _$mongoose.Types.ObjectId | string;
270
+ accountId?: import('mongoose').Types.ObjectId | string;
243
271
  ownerId?: string;
244
272
  tags: string[];
245
273
  metadata?: Record<string, unknown>;
274
+ /** GDPR tombstone — set by `anonymize()` when PII was scrubbed. */
275
+ anonymizedAt?: Date;
246
276
  createdAt: Date;
247
277
  updatedAt: Date;
248
278
  }
@@ -251,8 +281,8 @@ declare function createContactModel(connection: Connection, config: SchemaBuildC
251
281
  //#endregion
252
282
  //#region src/models/account.schema.d.ts
253
283
  interface IAccountDocument {
254
- _id: _$mongoose.Types.ObjectId;
255
- organizationId: _$mongoose.Types.ObjectId | string;
284
+ _id: import('mongoose').Types.ObjectId;
285
+ organizationId: import('mongoose').Types.ObjectId | string;
256
286
  name: string;
257
287
  domain?: string;
258
288
  industry?: string;
@@ -291,8 +321,8 @@ interface ICampaignStatusEntry {
291
321
  note?: string;
292
322
  }
293
323
  interface ICampaignDocument {
294
- _id: _$mongoose.Types.ObjectId;
295
- organizationId: _$mongoose.Types.ObjectId | string;
324
+ _id: import('mongoose').Types.ObjectId;
325
+ organizationId: import('mongoose').Types.ObjectId | string;
296
326
  name: string;
297
327
  description?: string;
298
328
  status: CampaignStatus;
@@ -342,10 +372,10 @@ interface ISubscriptionStatusEntry {
342
372
  note?: string;
343
373
  }
344
374
  interface ISubscriptionDocument {
345
- _id: _$mongoose.Types.ObjectId;
346
- organizationId: _$mongoose.Types.ObjectId | string;
347
- contactId: _$mongoose.Types.ObjectId | string;
348
- campaignId: _$mongoose.Types.ObjectId | string;
375
+ _id: import('mongoose').Types.ObjectId;
376
+ organizationId: import('mongoose').Types.ObjectId | string;
377
+ contactId: import('mongoose').Types.ObjectId | string;
378
+ campaignId: import('mongoose').Types.ObjectId | string;
349
379
  status: SubscriptionStatus;
350
380
  statusHistory: ISubscriptionStatusEntry[];
351
381
  source?: string;
@@ -376,9 +406,17 @@ type SuppressionReason = (typeof SUPPRESSION_REASONS)[number];
376
406
  //#endregion
377
407
  //#region src/models/suppression.schema.d.ts
378
408
  interface ISuppressionDocument {
379
- _id: _$mongoose.Types.ObjectId;
380
- organizationId: _$mongoose.Types.ObjectId | string;
381
- /** Stored lowercase-normalized (schema `lowercase: true` + repo trims). */
409
+ _id: import('mongoose').Types.ObjectId;
410
+ organizationId: import('mongoose').Types.ObjectId | string;
411
+ /**
412
+ * The CANONICAL address, as `@classytic/contact/email` defines it: trimmed,
413
+ * both sides lowercased, and the domain IDNA-folded to punycode. The
414
+ * repository canonicalises before every read and write — the schema's
415
+ * `lowercase: true` is a backstop for a direct model write, not the rule.
416
+ *
417
+ * `ada@bücher.de` is stored as `ada@xn--bcher-kva.de`. Two spellings of one
418
+ * host stored as two rows is a suppression that silently never matches.
419
+ */
382
420
  email: string;
383
421
  reason: SuppressionReason;
384
422
  /** Free-form origin: 'campaign:<id>', 'sequence:<id>', 'api', 'import', … */
@@ -415,12 +453,12 @@ type SubjectKind = (typeof SUBJECT_KINDS)[number];
415
453
  //#endregion
416
454
  //#region src/models/activity.schema.d.ts
417
455
  interface IActivityDocument {
418
- _id: _$mongoose.Types.ObjectId;
419
- organizationId: _$mongoose.Types.ObjectId | string;
456
+ _id: import('mongoose').Types.ObjectId;
457
+ organizationId: import('mongoose').Types.ObjectId | string;
420
458
  type: ActivityType;
421
459
  status: ActivityStatus;
422
460
  subjectKind: SubjectKind;
423
- subjectId: _$mongoose.Types.ObjectId | string;
461
+ subjectId: import('mongoose').Types.ObjectId | string;
424
462
  title: string;
425
463
  description?: string;
426
464
  ownerId?: string;
@@ -437,10 +475,10 @@ declare function createActivityModel(connection: Connection, config: SchemaBuild
437
475
  //#endregion
438
476
  //#region src/models/note.schema.d.ts
439
477
  interface INoteDocument {
440
- _id: _$mongoose.Types.ObjectId;
441
- organizationId: _$mongoose.Types.ObjectId | string;
478
+ _id: import('mongoose').Types.ObjectId;
479
+ organizationId: import('mongoose').Types.ObjectId | string;
442
480
  subjectKind: SubjectKind;
443
- subjectId: _$mongoose.Types.ObjectId | string;
481
+ subjectId: import('mongoose').Types.ObjectId | string;
444
482
  body: string;
445
483
  authorId?: string;
446
484
  pinned: boolean;
@@ -453,8 +491,8 @@ declare function createNoteModel(connection: Connection, config: SchemaBuildConf
453
491
  //#endregion
454
492
  //#region src/models/loss-reason.schema.d.ts
455
493
  interface ILossReasonDocument {
456
- _id: _$mongoose.Types.ObjectId;
457
- organizationId: _$mongoose.Types.ObjectId | string;
494
+ _id: import('mongoose').Types.ObjectId;
495
+ organizationId: import('mongoose').Types.ObjectId | string;
458
496
  name: string;
459
497
  description?: string;
460
498
  isActive: boolean;
@@ -501,12 +539,12 @@ type EmailEventKind = (typeof EMAIL_EVENT_KINDS)[number];
501
539
  declare const EMAIL_EVENT_SUBJECT_KINDS: readonly ["contact", "subscription"];
502
540
  type EmailEventSubjectKind = (typeof EMAIL_EVENT_SUBJECT_KINDS)[number];
503
541
  interface IEmailEventDocument {
504
- _id: _$mongoose.Types.ObjectId;
505
- organizationId: _$mongoose.Types.ObjectId | string;
542
+ _id: import('mongoose').Types.ObjectId;
543
+ organizationId: import('mongoose').Types.ObjectId | string;
506
544
  kind: EmailEventKind;
507
545
  subjectKind: EmailEventSubjectKind;
508
- subjectId: _$mongoose.Types.ObjectId | string;
509
- campaignId?: _$mongoose.Types.ObjectId | string;
546
+ subjectId: import('mongoose').Types.ObjectId | string;
547
+ campaignId?: import('mongoose').Types.ObjectId | string;
510
548
  messageId?: string;
511
549
  url?: string;
512
550
  userAgent?: string;
@@ -540,11 +578,11 @@ type MessageDeliveryStatus = (typeof MESSAGE_DELIVERY_STATUSES)[number];
540
578
  //#endregion
541
579
  //#region src/models/conversation.schema.d.ts
542
580
  interface IConversationDocument {
543
- _id: _$mongoose.Types.ObjectId;
544
- organizationId: _$mongoose.Types.ObjectId | string;
581
+ _id: import('mongoose').Types.ObjectId;
582
+ organizationId: import('mongoose').Types.ObjectId | string;
545
583
  channel: ConversationChannel;
546
584
  participantKind: ConversationParticipantKind;
547
- participantId: _$mongoose.Types.ObjectId | string;
585
+ participantId: import('mongoose').Types.ObjectId | string;
548
586
  /** Channel-side identifier (e.g. WhatsApp wa_id, Telegram chat id). */
549
587
  channelUserId?: string;
550
588
  status: ConversationStatus;
@@ -571,9 +609,9 @@ interface IMessageAttachment {
571
609
  mimeType?: string;
572
610
  }
573
611
  interface IMessageDocument {
574
- _id: _$mongoose.Types.ObjectId;
575
- organizationId: _$mongoose.Types.ObjectId | string;
576
- conversationId: _$mongoose.Types.ObjectId | string;
612
+ _id: import('mongoose').Types.ObjectId;
613
+ organizationId: import('mongoose').Types.ObjectId | string;
614
+ conversationId: import('mongoose').Types.ObjectId | string;
577
615
  direction: MessageDirection;
578
616
  body: string;
579
617
  attachments: IMessageAttachment[];
@@ -635,8 +673,8 @@ interface ISequenceStatusEntry {
635
673
  note?: string;
636
674
  }
637
675
  interface ISequenceDocument {
638
- _id: _$mongoose.Types.ObjectId;
639
- organizationId: _$mongoose.Types.ObjectId | string;
676
+ _id: import('mongoose').Types.ObjectId;
677
+ organizationId: import('mongoose').Types.ObjectId | string;
640
678
  name: string;
641
679
  description?: string;
642
680
  status: SequenceStatus;
@@ -687,11 +725,11 @@ interface IEnrollmentStatusEntry {
687
725
  by?: string;
688
726
  }
689
727
  interface IEnrollmentDocument {
690
- _id: _$mongoose.Types.ObjectId;
691
- organizationId: _$mongoose.Types.ObjectId | string;
692
- sequenceId: _$mongoose.Types.ObjectId | string;
728
+ _id: import('mongoose').Types.ObjectId;
729
+ organizationId: import('mongoose').Types.ObjectId | string;
730
+ sequenceId: import('mongoose').Types.ObjectId | string;
693
731
  subjectKind: EnrollmentSubjectKind;
694
- subjectId: _$mongoose.Types.ObjectId | string;
732
+ subjectId: import('mongoose').Types.ObjectId | string;
695
733
  status: EnrollmentStatus;
696
734
  /** Index into sequence.steps[] — 0-based. */
697
735
  currentStepIndex: number;
@@ -730,19 +768,27 @@ interface CrmModels {
730
768
  Sequence: Model<ISequenceDocument>;
731
769
  Enrollment: Model<IEnrollmentDocument>;
732
770
  }
733
- /** Mongoose model names this package registers — drives the §21 collision check. */
771
+ type ModularCrmModels = Partial<CrmModels>;
772
+ /** Mongoose model names this package can register (before `modelPrefix`). Reference/documentation. */
734
773
  declare const CRM_MODEL_NAMES: readonly ["Pipeline", "Lead", "Opportunity", "Contact", "Account", "Campaign", "EmailSubscription", "Suppression", "Activity", "Note", "LossReason", "EmailEvent", "Conversation", "Message", "Sequence", "Enrollment"];
735
- interface CreateModelsOptions extends SchemaBuildConfig {
774
+ interface DefineCrmModelsConfig extends SchemaBuildConfig {
775
+ /** Which model families to describe. A disabled family contributes no spec. */
776
+ modules: ResolvedCrmModules;
736
777
  /**
737
- * On a model-name collision: `false` (default) throws
738
- * `CrmModelCollisionError`; `true` deletes + re-registers (test
739
- * fixtures / hot reload only).
778
+ * On a model-name collision: `false` (default) uses mongokit's `throw`
779
+ * policy (`ModelCollisionError`); `true` uses `replace` (test fixtures /
780
+ * hot reload only — mongokit refuses `replace` under `NODE_ENV=production`).
740
781
  */
741
782
  forceRecreate?: boolean;
742
- /** Mongoose auto-index builds. Default `true` (dev); `false` in prod. */
783
+ /** Mongoose auto-index builds. Default `true` (dev); `false` in prod (§35). */
743
784
  autoIndex?: boolean;
744
785
  }
745
- declare function createCrmModels(connection: Connection, config: CreateModelsOptions): CrmModels;
786
+ /**
787
+ * Describe the CRM model set as a connection-free blueprint. Pure — builds no
788
+ * schema and registers nothing until `.bind(connection)`. Only the enabled
789
+ * module families contribute `ModelSpec` entries.
790
+ */
791
+ declare function defineCrmModels(config: DefineCrmModelsConfig): ModelBlueprint<ModularCrmModels>;
746
792
  //#endregion
747
793
  //#region src/repositories/pipeline.repository.d.ts
748
794
  interface PipelineRepositoryDeps {
@@ -809,6 +855,11 @@ declare class LeadRepository extends Repository<ILeadDocument> {
809
855
  * Create a Lead. Auto-scores on insert so hosts can route immediately.
810
856
  * Rejects duplicate emails inside the same tenant unless
811
857
  * `rejectDuplicates: false` was set at engine boot.
858
+ *
859
+ * Throws `LeadEmailFormatError` (400) on an email that cannot be canonicalised
860
+ * — nothing is written. The reason lives beside the helper in
861
+ * `core/email-identity.ts`, which also explains why the SCHEMA setter degrades
862
+ * where this verb refuses.
812
863
  */
813
864
  createLead(input: Partial<ILeadDocument>, ctx?: CrmContext): Promise<ILeadDocument>;
814
865
  markContacted(id: string, extras?: TransitionExtras, ctx?: CrmContext): Promise<ILeadDocument>;
@@ -845,6 +896,29 @@ declare class LeadRepository extends Repository<ILeadDocument> {
845
896
  * automatically; hosts also call this after enrichment writes.
846
897
  */
847
898
  recomputeScore(id: string, ctx?: CrmContext): Promise<ILeadDocument>;
899
+ /**
900
+ * The `converted` hop, owned by `LeadService.convert()` (the only caller
901
+ * — the four cross-repo writes make it a service concern). Kept on the
902
+ * repo so the atomic state write goes through `applyTransition` — a
903
+ * machine-guarded CAS that `$push`es the history entry instead of
904
+ * read-then-replacing the whole `statusHistory` array (no lost-update
905
+ * window). Deliberately emits NO `lead.status_changed`: `lead.converted`
906
+ * is the canonical signal for this transition and is dispatched by the
907
+ * service alongside the cross-repo refs.
908
+ */
909
+ markConverted(id: string, from: LeadStatus, patch: Partial<ILeadDocument>, entry: ILeadStatusEntry, ctx?: CrmContext): Promise<ILeadDocument>;
910
+ /**
911
+ * GDPR right-to-erasure for a Lead. Scrubs the PII fields
912
+ * (`fullName`/`firstName`/`lastName`/`email`/`phone`) in place while
913
+ * preserving non-PII aggregates (`score`, `status`, `statusHistory`,
914
+ * `engagement`, firmographic `companyName`/`source`) and the id. Sets
915
+ * `anonymizedAt` and emits `crm:lead.anonymized`.
916
+ *
917
+ * `fullName` is `required`, so it becomes the `'[redacted]'` sentinel;
918
+ * the optional PII scalars are nulled (dropping `email` out of the
919
+ * partial-unique email index). Idempotent.
920
+ */
921
+ anonymize(id: string, ctx?: CrmContext): Promise<ILeadDocument>;
848
922
  private _transition;
849
923
  /**
850
924
  * Count leads created on or after `since`. Used by dashboard KPI tiles.
@@ -937,15 +1011,21 @@ declare class OpportunityRepository extends Repository<IOpportunityDocument> {
937
1011
  /** Count opportunities with status `open`. Used by dashboard KPI tiles. */
938
1012
  countOpen(ctx?: CrmContext): Promise<number>;
939
1013
  /**
940
- * Sum `amount` on won opportunities updated within `since`.
941
- * Goes through the tenant-scoped `findAll()` — the multiTenant plugin
942
- * injects `organizationId`, so a missing ctx can't leak other tenants'
943
- * revenue (a manual `if (ctx)` filter would).
1014
+ * Sum `amount` on won opportunities updated on/after `since`, grouped by
1015
+ * currency. Returns one row per currency (descending by total) — a deal
1016
+ * book is multi-currency, so collapsing to a single total would silently
1017
+ * drop every non-top currency's revenue.
1018
+ *
1019
+ * Uses `aggregatePipeline` (a DB-side `$group`) instead of loading every
1020
+ * won opp into memory: the multiTenant + soft-delete policy hooks prepend
1021
+ * their `$match` so a missing ctx can't leak other tenants' revenue, and
1022
+ * the row count is bounded by the number of distinct currencies, not the
1023
+ * number of deals.
944
1024
  */
945
- sumWonSince(since: Date, ctx?: CrmContext): Promise<{
1025
+ sumWonSince(since: Date, ctx?: CrmContext): Promise<Array<{
1026
+ currency: string;
946
1027
  total: number;
947
- currency?: string;
948
- } | null>;
1028
+ }>>;
949
1029
  /** Latest `limit` opportunities — lightweight projection for the activity feed. */
950
1030
  findRecentForFeed(limit: number, ctx?: CrmContext): Promise<IOpportunityDocument[]>;
951
1031
  }
@@ -953,7 +1033,23 @@ declare class OpportunityRepository extends Repository<IOpportunityDocument> {
953
1033
  //#region src/repositories/contact.repository.d.ts
954
1034
  interface ContactRepositoryDeps {
955
1035
  events: CrmEventDispatcher;
1036
+ /** Reject `createContact()` when an email already exists in-tenant (default true). */
1037
+ rejectDuplicates?: boolean;
956
1038
  }
1039
+ /**
1040
+ * Optional synchronous re-point hook fired INSIDE `merge()`, BEFORE the
1041
+ * secondary is deleted — inside the caller's `ctx.session` when one is
1042
+ * passed. Use it to move the doomed secondary's FKs onto the primary
1043
+ * (EmailSubscription
1044
+ * `.contactId` is `required`, plus Opportunity `.primaryContactId`,
1045
+ * Activity/Note `.subjectId`) so the delete never orphans a required
1046
+ * reference. Throwing here aborts the merge — nothing is deleted.
1047
+ */
1048
+ type ContactMergeRepoint = (args: {
1049
+ primaryId: string;
1050
+ secondaryId: string;
1051
+ ctx?: CrmContext;
1052
+ }) => Promise<void> | void;
957
1053
  interface MergeContactInput {
958
1054
  /** Surviving contact id. Receives merged fields. */
959
1055
  primaryId: string;
@@ -965,13 +1061,30 @@ interface MergeContactInput {
965
1061
  * for the reverse. `tags` are always unioned.
966
1062
  */
967
1063
  strategy?: 'preferPrimary' | 'preferSecondary';
1064
+ /**
1065
+ * Optional FK re-point seam — see {@link ContactMergeRepoint}. Pass this
1066
+ * to atomically move the secondary's required references onto the primary
1067
+ * before it is deleted. When omitted, re-pointing stays the host's job
1068
+ * via the async `crm:contact.merged` event (the legacy contract).
1069
+ */
1070
+ repoint?: ContactMergeRepoint;
968
1071
  }
969
1072
  declare class ContactRepository extends Repository<IContactDocument> {
970
1073
  private readonly events;
1074
+ private readonly rejectDuplicates;
971
1075
  constructor(model: Model<IContactDocument>, plugins: PluginType[], deps: ContactRepositoryDeps, pagination?: PaginationConfig);
972
1076
  /**
973
1077
  * Create + emit `crm:contact.created`. Hosts call this instead of
974
1078
  * `create()` when they want the event side-effect (e.g. webhook fan-out).
1079
+ *
1080
+ * Rejects duplicate emails inside the same tenant unless
1081
+ * `rejectDuplicates: false` was set — mirrors `LeadRepository.createLead`.
1082
+ * The partial-unique `(organizationId, contact.email)` index is the
1083
+ * authoritative guard; the pre-check only closes the fast path.
1084
+ *
1085
+ * Throws `ContactEmailFormatError` (400) on an email that cannot be
1086
+ * canonicalised — nothing is written. See `core/email-identity.ts` for the
1087
+ * reason, including what it means for `LeadService.convert()`.
975
1088
  */
976
1089
  createContact(input: Partial<IContactDocument>, ctx?: CrmContext): Promise<IContactDocument>;
977
1090
  /**
@@ -997,6 +1110,22 @@ declare class ContactRepository extends Repository<IContactDocument> {
997
1110
  * from there.
998
1111
  */
999
1112
  merge(input: MergeContactInput, ctx?: CrmContext): Promise<IContactDocument>;
1113
+ /**
1114
+ * GDPR right-to-erasure. Scrubs every PII field on the contact in place —
1115
+ * the structured `name` and ALL `contact.*` channels (email,
1116
+ * personalEmail, phone, alternatePhone, whatsapp, telegram, linkedin,
1117
+ * instagram) — while preserving the id and non-PII aggregates
1118
+ * (`accountId`, `ownerId`, `tags`, timestamps) so historical rollups and
1119
+ * relationships survive. Sets `anonymizedAt` as a tombstone and emits
1120
+ * `crm:contact.anonymized`. Idempotent: a second call re-scrubs to the
1121
+ * same sentinel.
1122
+ *
1123
+ * `name.given` is `required`, so it becomes the `'[redacted]'` sentinel
1124
+ * rather than empty; every other name part and the whole `contact`
1125
+ * subdocument are cleared. Unsetting `contact.email` also drops the row
1126
+ * out of the partial-unique email index, freeing the address for reuse.
1127
+ */
1128
+ anonymize(id: string, ctx?: CrmContext): Promise<IContactDocument>;
1000
1129
  }
1001
1130
  //#endregion
1002
1131
  //#region src/repositories/account.repository.d.ts
@@ -1084,6 +1213,15 @@ interface SuppressInput {
1084
1213
  subjectId?: string;
1085
1214
  metadata?: Record<string, unknown>;
1086
1215
  }
1216
+ /**
1217
+ * The suppression key for `email`, or `null` when it cannot be canonicalised.
1218
+ *
1219
+ * Exported because it is the ONLY way a caller can distinguish "this address is on
1220
+ * the list" from "this address is not an address" — the read verbs collapse both
1221
+ * to `true` on purpose (see the fail-closed rule in the module docblock), and a
1222
+ * caller that needs the distinction should not re-derive the rule to get it.
1223
+ */
1224
+ declare function suppressionEmailKey(email: string): string | null;
1087
1225
  declare class SuppressionRepository extends Repository<ISuppressionDocument> {
1088
1226
  private readonly events;
1089
1227
  constructor(model: Model<ISuppressionDocument>, plugins: PluginType[], deps: SuppressionRepositoryDeps, pagination?: PaginationConfig);
@@ -1092,18 +1230,36 @@ declare class SuppressionRepository extends Repository<ISuppressionDocument> {
1092
1230
  * `getOrCreate` on the unique (org, email) index — of N concurrent
1093
1231
  * calls exactly one inserts. Emits `crm:suppression.added` only for
1094
1232
  * the inserting call; an existing row is returned unchanged, no event.
1233
+ *
1234
+ * Throws `SuppressionEmailFormatError` (400) on an address that cannot be
1235
+ * canonicalised — nothing is written.
1095
1236
  */
1096
1237
  suppress(email: string, input: SuppressInput, ctx?: CrmContext): Promise<ISuppressionDocument>;
1097
- /** True when `email` is on the org's suppression list. */
1238
+ /**
1239
+ * True when `email` is on the org's suppression list — **or when it cannot be
1240
+ * canonicalised at all**, which is the fail-closed half of the module rule
1241
+ * above. Never throws on a malformed address: the safe answer is "do not send",
1242
+ * and this verb's job is to produce that answer, not to police the input.
1243
+ */
1098
1244
  isSuppressed(email: string, ctx?: CrmContext): Promise<boolean>;
1099
1245
  /**
1100
- * Return the suppressed subset of `emails` (normalized) as a Set —
1101
- * ONE `$in` query, built for the campaign-fanout hot path:
1246
+ * Return the suppressed subset of `emails` as a Set — ONE `$in` query, built for
1247
+ * the campaign-fanout hot path:
1102
1248
  *
1103
1249
  * ```ts
1104
1250
  * const blocked = await suppression.filterSuppressed(recipients, ctx);
1105
- * const sendable = recipients.filter((e) => !blocked.has(e.toLowerCase()));
1251
+ * const sendable = recipients.filter((e) => !blocked.has(suppressionEmailKey(e) ?? e));
1106
1252
  * ```
1253
+ *
1254
+ * The Set is keyed CANONICALLY, so a caller must canonicalise its own recipient
1255
+ * before testing membership — matching on the raw spelling is how the punycode
1256
+ * miss came back. `suppressionEmailKey` is exported for exactly that, and returns
1257
+ * `null` for an address with no canonical form.
1258
+ *
1259
+ * An address that cannot be canonicalised is reported as BLOCKED, keyed by the
1260
+ * caller's original spelling (there is no canonical form to key it by, and the
1261
+ * caller has to be able to find it). It is not thrown: one malformed recipient
1262
+ * must not abort a fanout to thousands of valid ones.
1107
1263
  */
1108
1264
  filterSuppressed(emails: string[], ctx?: CrmContext): Promise<Set<string>>;
1109
1265
  /**
@@ -1111,6 +1267,11 @@ declare class SuppressionRepository extends Repository<ISuppressionDocument> {
1111
1267
  * `deleteMany` on the (unique) email key — emits
1112
1268
  * `crm:suppression.removed` only when a row was actually removed.
1113
1269
  * Returns whether a row was removed.
1270
+ *
1271
+ * Throws `SuppressionEmailFormatError` (400) on an address that cannot be
1272
+ * canonicalised, rather than answering `false`. This is the verb that re-enables
1273
+ * sending, and `false` would read as "already removed" when the truth is "that
1274
+ * was never an address" — an operator would close the ticket either way.
1114
1275
  */
1115
1276
  unsuppress(email: string, ctx?: CrmContext): Promise<boolean>;
1116
1277
  }
@@ -1342,65 +1503,81 @@ interface CrmLogger {
1342
1503
  error?(msg: string, meta?: Record<string, unknown>): void;
1343
1504
  debug?(msg: string, meta?: Record<string, unknown>): void;
1344
1505
  }
1345
- interface CrmConfig {
1346
- /** Mongoose connection — package creates models on this connection. */
1347
- connection: Connection;
1506
+ /**
1507
+ * DESCRIBE-time shape (Kernel Construction Standard §6.2) — everything that
1508
+ * determines the persistence SHAPE and needs no connection or I/O to inspect.
1509
+ * Passed to `defineCrm(shape)`; nothing here opens a socket or reads a secret.
1510
+ */
1511
+ interface CrmShape {
1348
1512
  /**
1349
1513
  * Tenant column shape. `'objectId'` (default) matches Better Auth orgs
1350
1514
  * and unlocks `$lookup` / `.populate()`; `'string'` matches UUID hosts.
1351
1515
  * Threaded into both schemas and `multiTenantPlugin` so they stay in
1352
1516
  * sync (PACKAGE_RULES §9.2).
1353
1517
  */
1354
- tenantFieldType?: TenantFieldType;
1518
+ tenant?: boolean | TenantConfig | undefined;
1355
1519
  /**
1356
- * When `true` (default), wire `multiTenantPlugin` on every repository
1357
- * with `required: true`. Set `false` only for fixtures / smoke tests
1358
- * that intentionally drop the scope.
1520
+ * Prefix for Mongoose model registry names (for example `Crm` produces
1521
+ * `CrmAccount`). Use in large hosts where generic names such as `Account`
1522
+ * are already owned by accounting or identity modules.
1359
1523
  */
1360
- multiTenant?: boolean;
1361
- /** Arc-compatible event transport. Default: in-process bus. */
1362
- eventTransport?: EventTransport;
1524
+ modelPrefix?: string | undefined;
1363
1525
  /**
1364
- * Transactional outbox (PACKAGE_RULES §P8). When wired, every domain
1365
- * event is persisted via `outbox.save(event, { session })` in the SAME
1366
- * session as the business write — the host's relay publishes committed
1367
- * rows. Without it, events publish post-write (or defer to a
1368
- * `ctx[PENDING_EVENTS]` queue inside `engine.withTransaction`).
1526
+ * Progressive feature bundles (PACKAGE_RULES §22). Omit for the
1527
+ * all-enabled engine. When provided, unspecified modules are disabled;
1528
+ * dependencies are enabled automatically.
1369
1529
  */
1370
- outbox?: OutboxStore;
1371
- /** Logger surface used by the in-process bus + repositories. */
1372
- logger?: CrmLogger;
1373
- /** Pagination defaults for every repository (mongokit). */
1374
- pagination?: PaginationConfig;
1375
- /** Extra mongokit plugins applied to every repository (after defaults). */
1376
- plugins?: PluginType[];
1530
+ modules?: CrmModulesConfig | undefined;
1377
1531
  /**
1378
1532
  * On a Mongoose model-name collision (another engine / hot reload owns
1379
- * the name): `false` (default) throws `CrmModelCollisionError`; `true`
1380
- * deletes + re-registers. Test fixtures / hot reload only (§21).
1533
+ * the name): `false` (default) throws mongokit's `ModelCollisionError`;
1534
+ * `true` re-registers. Test fixtures / hot reload only (§21).
1381
1535
  */
1382
- forceRecreate?: boolean;
1536
+ forceRecreate?: boolean | undefined;
1383
1537
  /**
1384
1538
  * Mongoose auto-index builds. Default `true` (dev). Set `false` in
1385
1539
  * production and run `engine.syncIndexes()` from a deploy script so
1386
1540
  * cold-boot index builds don't blow Fastify's plugin timeout (§35).
1387
1541
  */
1388
- autoIndex?: boolean;
1542
+ autoIndex?: boolean | undefined;
1543
+ }
1544
+ /**
1545
+ * BIND-time runtime collaborators (Kernel Construction Standard §6.2). The live
1546
+ * connection is the first `bind` arg; these are the rest. A transport, outbox,
1547
+ * logger or plugin set belongs here, not in the shape.
1548
+ */
1549
+ interface CrmRuntime {
1550
+ /** Arc-compatible event transport. Default: in-process bus (owned + closed by the engine). */
1551
+ eventTransport?: EventTransport | undefined;
1552
+ /**
1553
+ * Transactional outbox (PACKAGE_RULES §P8). When wired, every domain
1554
+ * event is persisted via `outbox.save(event, { session })` in the SAME
1555
+ * session as the business write — the host's relay publishes committed
1556
+ * rows. Without it, events publish post-write (or defer to a
1557
+ * `ctx[PENDING_EVENTS]` queue inside `engine.withTransaction`).
1558
+ */
1559
+ outbox?: OutboxStore | undefined;
1560
+ /** Logger surface used by the in-process bus + repositories. */
1561
+ logger?: CrmLogger | undefined;
1562
+ /** Pagination defaults for every repository (mongokit). */
1563
+ pagination?: PaginationConfig | undefined;
1564
+ /** Extra mongokit plugins applied to every repository (after defaults). */
1565
+ plugins?: PluginType[] | undefined;
1389
1566
  /**
1390
1567
  * Skip the boot-time `transactions` capability requirement, downgrading
1391
1568
  * it to a warning. Set only when the host never calls
1392
1569
  * `LeadService.convert()` inside a transaction (standalone Mongo).
1393
1570
  */
1394
- allowNonTransactional?: boolean;
1571
+ allowNonTransactional?: boolean | undefined;
1395
1572
  }
1396
1573
  interface ResolvedCrmConfig extends SchemaBuildConfig {
1397
1574
  connection: Connection;
1398
- tenantFieldType: TenantFieldType;
1399
- multiTenant: boolean;
1575
+ tenant: ResolvedTenantConfig;
1400
1576
  forceRecreate: boolean;
1401
1577
  autoIndex: boolean;
1402
- logger?: CrmLogger;
1403
- pagination?: PaginationConfig;
1578
+ modules: ResolvedCrmModules;
1579
+ logger?: CrmLogger | undefined;
1580
+ pagination?: PaginationConfig | undefined;
1404
1581
  plugins: PluginType[];
1405
1582
  }
1406
1583
  interface CrmRepositories {
@@ -1421,9 +1598,30 @@ interface CrmRepositories {
1421
1598
  sequence: SequenceRepository;
1422
1599
  enrollment: EnrollmentRepository;
1423
1600
  }
1424
- interface CrmEngine {
1601
+ type ModularCrmRepositories = Partial<CrmRepositories>;
1602
+ type SalesCrmModels = Pick<CrmModels, 'Pipeline' | 'Lead' | 'Opportunity' | 'Contact' | 'Account' | 'Activity' | 'Note' | 'LossReason'>;
1603
+ type SalesCrmRepositories = Pick<CrmRepositories, 'pipeline' | 'lead' | 'opportunity' | 'contact' | 'account' | 'activity' | 'note' | 'lossReason'>;
1604
+ type EngagementCrmModels = Pick<CrmModels, 'Subscription' | 'Suppression' | 'EmailEvent' | 'Conversation' | 'Message'>;
1605
+ type EngagementCrmRepositories = Pick<CrmRepositories, 'subscription' | 'suppression' | 'emailEvent' | 'conversation' | 'message'>;
1606
+ type AutomationCrmModels = Pick<CrmModels, 'Campaign' | 'Sequence' | 'Enrollment'>;
1607
+ type AutomationCrmRepositories = Pick<CrmRepositories, 'campaign' | 'sequence' | 'enrollment'>;
1608
+ interface CrmModuleView<TModels, TRepositories> {
1609
+ readonly models: Readonly<TModels>;
1610
+ readonly repositories: Readonly<TRepositories>;
1611
+ }
1612
+ interface ModularCrmModuleViews {
1613
+ readonly sales?: CrmModuleView<SalesCrmModels, SalesCrmRepositories> | undefined;
1614
+ readonly engagement?: CrmModuleView<EngagementCrmModels, EngagementCrmRepositories> | undefined;
1615
+ readonly automation?: CrmModuleView<AutomationCrmModels, AutomationCrmRepositories> | undefined;
1616
+ }
1617
+ interface FullCrmModuleViews {
1618
+ readonly sales: CrmModuleView<SalesCrmModels, SalesCrmRepositories>;
1619
+ readonly engagement: CrmModuleView<EngagementCrmModels, EngagementCrmRepositories>;
1620
+ readonly automation: CrmModuleView<AutomationCrmModels, AutomationCrmRepositories>;
1621
+ }
1622
+ interface CrmEngineBase<TModels, TRepositories, TModules> {
1425
1623
  /** Repositories ARE the API surface (PACKAGE_RULES §1). */
1426
- readonly repositories: Readonly<CrmRepositories>;
1624
+ readonly repositories: Readonly<TRepositories>;
1427
1625
  /**
1428
1626
  * Arc-compatible event transport (a `CrmEventDispatcher` wrapping the
1429
1627
  * configured transport — `publish`/`subscribe` behave exactly like the
@@ -1431,7 +1629,9 @@ interface CrmEngine {
1431
1629
  */
1432
1630
  readonly events: CrmEventDispatcher;
1433
1631
  /** Mongoose models — for arc adapter wiring. */
1434
- readonly models: Readonly<CrmModels>;
1632
+ readonly models: Readonly<TModels>;
1633
+ /** Capability-oriented views for hosts that opt into progressive modules. */
1634
+ readonly modules: Readonly<TModules>;
1435
1635
  /** Resolved configuration (frozen). */
1436
1636
  readonly config: Readonly<ResolvedCrmConfig>;
1437
1637
  /**
@@ -1453,8 +1653,34 @@ interface CrmEngine {
1453
1653
  * deploy when booted with `autoIndex: false` (§35). Idempotent.
1454
1654
  */
1455
1655
  syncIndexes(): Promise<void>;
1456
- /** Release resources. Safe to call multiple times. */
1457
- dispose(): Promise<void>;
1656
+ /**
1657
+ * Tear the engine down (Kernel Construction Standard §6.4 — the sole teardown
1658
+ * name). Closes the event bus ONLY when the engine created it itself
1659
+ * (`InProcessCrmBus`); a host-injected `eventTransport` is the host's to
1660
+ * close. Idempotent — a second call is a no-op. Mongoose
1661
+ * models/connection are untouched.
1662
+ */
1663
+ close(): Promise<void>;
1664
+ }
1665
+ /** All-enabled engine returned when `shape.modules` is omitted. */
1666
+ type CrmEngine = CrmEngineBase<CrmModels, CrmRepositories, FullCrmModuleViews>;
1667
+ /** Progressive engine returned when a `modules` selection is supplied. */
1668
+ type ModularCrmEngine = CrmEngineBase<ModularCrmModels, ModularCrmRepositories, ModularCrmModuleViews>;
1669
+ /**
1670
+ * The connection-free description produced by `defineCrm(shape)` (Kernel
1671
+ * Construction Standard §6.1). `defineCrm` is the SOLE construction API — there
1672
+ * is no `createCrm` wrapper. `bind` compiles the schemas on the supplied
1673
+ * connection, wires the engine, verifies capabilities, and returns the engine.
1674
+ */
1675
+ interface CrmBlueprint<TEngine extends CrmEngine | ModularCrmEngine = ModularCrmEngine> {
1676
+ readonly id: 'crm';
1677
+ /** The model names this blueprint registers — known without a connection. */
1678
+ readonly modelNames: readonly string[];
1679
+ /**
1680
+ * Compile + register the models on `connection`, wire repositories/transport,
1681
+ * verify capabilities, and return the frozen engine.
1682
+ */
1683
+ bind(connection: Connection, runtime?: CrmRuntime): TEngine;
1458
1684
  }
1459
1685
  /**
1460
1686
  * Operation context threaded through every domain verb. Extends
@@ -1463,22 +1689,28 @@ interface CrmEngine {
1463
1689
  * `metadata?` field — keep the typed surface tight.
1464
1690
  */
1465
1691
  interface CrmContext extends OperationContext {
1466
- organizationId?: string | _$mongoose.Types.ObjectId;
1467
- actorId?: string | _$mongoose.Types.ObjectId;
1692
+ organizationId?: string | import('mongoose').Types.ObjectId;
1693
+ actorId?: string | import('mongoose').Types.ObjectId;
1468
1694
  /** Mongo transaction session (passed through to mongokit). */
1469
1695
  session?: unknown;
1470
1696
  }
1471
1697
  type _ModelTypes = Model<unknown>;
1472
1698
  //#endregion
1473
- //#region src/engine/create-crm.d.ts
1474
- declare function createCrm(config: CrmConfig): Promise<CrmEngine>;
1699
+ //#region src/engine/define-crm.d.ts
1700
+ declare function defineCrm(shape: CrmShape & {
1701
+ modules: CrmModulesConfig;
1702
+ }): CrmBlueprint<ModularCrmEngine>;
1703
+ declare function defineCrm(shape?: CrmShape & {
1704
+ modules?: undefined;
1705
+ }): CrmBlueprint<CrmEngine>;
1706
+ declare function defineCrm(shape?: CrmShape): CrmBlueprint<ModularCrmEngine>;
1475
1707
  //#endregion
1476
1708
  //#region src/repositories/create-repositories.d.ts
1477
1709
  interface CreateCrmRepositoriesDeps {
1478
1710
  events: CrmEventDispatcher;
1479
1711
  config: ResolvedCrmConfig;
1480
1712
  }
1481
- declare function createCrmRepositories(models: CrmModels, deps: CreateCrmRepositoriesDeps): CrmRepositories;
1713
+ declare function createCrmRepositories(models: ModularCrmModels, deps: CreateCrmRepositoriesDeps): ModularCrmRepositories;
1482
1714
  //#endregion
1483
1715
  //#region src/services/lead.service.d.ts
1484
1716
  interface ConvertLeadInput {
@@ -1598,9 +1830,11 @@ declare const CRM_EVENTS: {
1598
1830
  readonly EMAIL_EVENT_UNSUBSCRIBED: "crm:email_event.unsubscribed";
1599
1831
  readonly LEAD_ENRICHED: "crm:lead.enriched";
1600
1832
  readonly LEAD_ASSIGNED: "crm:lead.assigned";
1833
+ readonly LEAD_ANONYMIZED: "crm:lead.anonymized";
1601
1834
  readonly CONTACT_ENRICHED: "crm:contact.enriched";
1602
1835
  readonly CONTACT_ASSIGNED: "crm:contact.assigned";
1603
1836
  readonly CONTACT_MERGED: "crm:contact.merged";
1837
+ readonly CONTACT_ANONYMIZED: "crm:contact.anonymized";
1604
1838
  readonly ACCOUNT_ENRICHED: "crm:account.enriched";
1605
1839
  readonly ACCOUNT_ASSIGNED: "crm:account.assigned";
1606
1840
  readonly ACCOUNT_MERGED: "crm:account.merged";
@@ -1666,6 +1900,10 @@ declare const LeadAssigned: CrmEventDefinition<z.ZodObject<{
1666
1900
  ownerId: z.ZodString;
1667
1901
  previousOwnerId: z.ZodOptional<z.ZodString>;
1668
1902
  }, z.core.$strip>>;
1903
+ declare const LeadAnonymized: CrmEventDefinition<z.ZodObject<{
1904
+ leadId: z.ZodString;
1905
+ fields: z.ZodArray<z.ZodString>;
1906
+ }, z.core.$strip>>;
1669
1907
  declare const AccountCreated: CrmEventDefinition<z.ZodObject<{
1670
1908
  accountId: z.ZodString;
1671
1909
  name: z.ZodString;
@@ -1716,6 +1954,10 @@ declare const ContactMerged: CrmEventDefinition<z.ZodObject<{
1716
1954
  preferSecondary: "preferSecondary";
1717
1955
  }>;
1718
1956
  }, z.core.$strip>>;
1957
+ declare const ContactAnonymized: CrmEventDefinition<z.ZodObject<{
1958
+ contactId: z.ZodString;
1959
+ fields: z.ZodArray<z.ZodString>;
1960
+ }, z.core.$strip>>;
1719
1961
  declare const OpportunityCreated: CrmEventDefinition<z.ZodObject<{
1720
1962
  opportunityId: z.ZodString;
1721
1963
  pipelineId: z.ZodString;
@@ -1984,16 +2226,10 @@ declare const crmEventDefinitions: ReadonlyArray<CrmEventDefinition>;
1984
2226
  declare function createEvent<T>(type: string, payload: T, ctx?: CrmContext, meta?: Partial<EventMeta>): DomainEvent<T>;
1985
2227
  //#endregion
1986
2228
  //#region src/events/in-process-bus.d.ts
1987
- declare class InProcessCrmBus implements EventTransport {
1988
- readonly name = "in-process-crm";
1989
- private subs;
1990
- private logger?;
2229
+ declare class InProcessCrmBus extends InProcessEventBus {
1991
2230
  constructor(options?: {
1992
2231
  logger?: CrmLogger;
1993
2232
  });
1994
- publish(event: DomainEvent): Promise<void>;
1995
- subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
1996
- close(): Promise<void>;
1997
2233
  }
1998
2234
  //#endregion
1999
2235
  //#region src/engine/run-unit.d.ts
@@ -2023,27 +2259,40 @@ declare function runUnit<T>(config: RunUnitConfig, ctx: CrmContext, body: (inner
2023
2259
  * Built on `@classytic/primitives/state-machine` so the same shape lights
2024
2260
  * up across kits (mongokit's `claim()` pairing, sqlitekit follow-ups).
2025
2261
  */
2026
- declare const LEAD_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"new" | "contacted" | "qualified" | "converted" | "disqualified" | "nurturing">;
2027
- declare const OPPORTUNITY_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"open" | "won" | "lost" | "abandoned">;
2028
- declare const CAMPAIGN_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"draft" | "active" | "paused" | "completed" | "archived">;
2029
- declare const SUBSCRIPTION_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"pending" | "confirmed" | "unsubscribed" | "bounced" | "complained">;
2030
- declare const ACTIVITY_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"completed" | "planned" | "in_progress" | "cancelled">;
2031
- declare const SEQUENCE_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"draft" | "active" | "paused" | "archived">;
2032
- declare const ENROLLMENT_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"active" | "paused" | "completed" | "exited">;
2262
+ declare const LEAD_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"new" | "contacted" | "qualified" | "converted" | "disqualified" | "nurturing">;
2263
+ declare const OPPORTUNITY_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"open" | "won" | "lost" | "abandoned">;
2264
+ declare const CAMPAIGN_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"draft" | "active" | "paused" | "completed" | "archived">;
2265
+ declare const SUBSCRIPTION_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"pending" | "confirmed" | "unsubscribed" | "bounced" | "complained">;
2266
+ declare const ACTIVITY_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"completed" | "planned" | "in_progress" | "cancelled">;
2267
+ declare const SEQUENCE_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"draft" | "active" | "paused" | "archived">;
2268
+ declare const ENROLLMENT_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"active" | "paused" | "completed" | "exited">;
2033
2269
  //#endregion
2034
2270
  //#region src/core/errors.d.ts
2035
2271
  /**
2036
- * CRM error vocabulary. Hosts catch `CrmError` at API edges.
2272
+ * `status` makes every CRM throwable satisfy `HttpError` from
2273
+ * `@classytic/repo-core/errors` (PACKAGE_RULES "Errors and wire envelopes"),
2274
+ * so arc's global handler serializes it natively with the right code AND the
2275
+ * right status — no per-host `errorMapper`. Without it every CRM error
2276
+ * reached the client as `arc.internal_error` / 500: `NotFoundError` answered
2277
+ * 500 instead of 404, which tells a caller "the server broke" when the truth
2278
+ * was "that lead doesn't exist".
2279
+ *
2280
+ * Defaults to 422 (well-formed request, violates a domain invariant) —
2281
+ * the common case here. Subclasses override with `readonly status = N`;
2282
+ * the subclass field initializer runs after this constructor, so it wins.
2037
2283
  */
2038
- declare class CrmError extends Error {
2284
+ declare class CrmError extends Error implements HttpError {
2039
2285
  readonly code: string;
2286
+ readonly status: number;
2040
2287
  readonly details?: Record<string, unknown>;
2041
2288
  constructor(code: string, message: string, details?: Record<string, unknown>);
2042
2289
  }
2043
2290
  declare class NotFoundError extends CrmError {
2291
+ readonly status = 404;
2044
2292
  constructor(kind: string, id: string);
2045
2293
  }
2046
2294
  declare class InvalidLeadTransitionError extends CrmError {
2295
+ readonly status = 422;
2047
2296
  constructor(details: {
2048
2297
  from: string;
2049
2298
  to: string;
@@ -2051,6 +2300,7 @@ declare class InvalidLeadTransitionError extends CrmError {
2051
2300
  });
2052
2301
  }
2053
2302
  declare class InvalidOpportunityTransitionError extends CrmError {
2303
+ readonly status = 422;
2054
2304
  constructor(details: {
2055
2305
  from: string;
2056
2306
  to: string;
@@ -2058,6 +2308,7 @@ declare class InvalidOpportunityTransitionError extends CrmError {
2058
2308
  });
2059
2309
  }
2060
2310
  declare class InvalidActivityTransitionError extends CrmError {
2311
+ readonly status = 422;
2061
2312
  constructor(details: {
2062
2313
  from: string;
2063
2314
  to: string;
@@ -2065,27 +2316,39 @@ declare class InvalidActivityTransitionError extends CrmError {
2065
2316
  });
2066
2317
  }
2067
2318
  declare class StageNotInPipelineError extends CrmError {
2319
+ readonly status = 422;
2068
2320
  constructor(details: {
2069
2321
  pipelineId: string;
2070
2322
  stageId: string;
2071
2323
  });
2072
2324
  }
2073
2325
  declare class LeadAlreadyConvertedError extends CrmError {
2326
+ readonly status = 409;
2074
2327
  constructor(leadId: string);
2075
2328
  }
2076
2329
  declare class DuplicateLeadError extends CrmError {
2330
+ readonly status = 409;
2077
2331
  constructor(details: {
2078
2332
  email?: string;
2079
2333
  phone?: string;
2080
2334
  });
2081
2335
  }
2336
+ declare class DuplicateContactError extends CrmError {
2337
+ readonly status = 409;
2338
+ constructor(details: {
2339
+ email: string;
2340
+ });
2341
+ }
2082
2342
  declare class UnmanagedSessionError extends CrmError {
2343
+ readonly status = 500;
2083
2344
  constructor(eventType?: string);
2084
2345
  }
2085
2346
  declare class EmptyPipelineError extends CrmError {
2347
+ readonly status = 422;
2086
2348
  constructor(pipelineId: string);
2087
2349
  }
2088
2350
  declare class InvalidCampaignTransitionError extends CrmError {
2351
+ readonly status = 422;
2089
2352
  constructor(details: {
2090
2353
  from: string;
2091
2354
  to: string;
@@ -2093,9 +2356,11 @@ declare class InvalidCampaignTransitionError extends CrmError {
2093
2356
  });
2094
2357
  }
2095
2358
  declare class DuplicateCampaignError extends CrmError {
2359
+ readonly status = 409;
2096
2360
  constructor(name: string);
2097
2361
  }
2098
2362
  declare class InvalidSubscriptionTransitionError extends CrmError {
2363
+ readonly status = 422;
2099
2364
  constructor(details: {
2100
2365
  from: string;
2101
2366
  to: string;
@@ -2103,18 +2368,22 @@ declare class InvalidSubscriptionTransitionError extends CrmError {
2103
2368
  });
2104
2369
  }
2105
2370
  declare class DuplicateSubscriptionError extends CrmError {
2371
+ readonly status = 409;
2106
2372
  constructor(details: {
2107
2373
  contactId: string;
2108
2374
  campaignId: string;
2109
2375
  });
2110
2376
  }
2111
2377
  declare class InvalidConfirmationTokenError extends CrmError {
2378
+ readonly status = 422;
2112
2379
  constructor();
2113
2380
  }
2114
2381
  declare class InvalidUnsubscribeTokenError extends CrmError {
2382
+ readonly status = 422;
2115
2383
  constructor();
2116
2384
  }
2117
2385
  declare class InvalidSequenceTransitionError extends CrmError {
2386
+ readonly status = 422;
2118
2387
  constructor(details: {
2119
2388
  from: string;
2120
2389
  to: string;
@@ -2122,6 +2391,7 @@ declare class InvalidSequenceTransitionError extends CrmError {
2122
2391
  });
2123
2392
  }
2124
2393
  declare class InvalidEnrollmentTransitionError extends CrmError {
2394
+ readonly status = 422;
2125
2395
  constructor(details: {
2126
2396
  from: string;
2127
2397
  to: string;
@@ -2129,11 +2399,111 @@ declare class InvalidEnrollmentTransitionError extends CrmError {
2129
2399
  });
2130
2400
  }
2131
2401
  declare class AlreadyEnrolledError extends CrmError {
2402
+ readonly status = 409;
2132
2403
  constructor(details: {
2133
2404
  sequenceId: string;
2134
2405
  subjectId: string;
2135
2406
  });
2136
2407
  }
2408
+ /**
2409
+ * An address handed to a suppression-list WRITE could not be canonicalised.
2410
+ *
2411
+ * 400, not 422 — the request itself is malformed at the field, no domain
2412
+ * invariant was consulted. This is `@classytic/contact`'s `EmailFormatError`
2413
+ * re-thrown as a CRM error so arc serializes it natively; the same shape party
2414
+ * uses for `PartyPhoneFormatError`. `contact` sits below every domain kernel
2415
+ * and knows nothing about HTTP, so the mapping has to happen here.
2416
+ *
2417
+ * Only WRITES throw. Reads fail closed instead — see
2418
+ * `suppression.repository.ts`, which writes the asymmetry down.
2419
+ */
2420
+ declare class SuppressionEmailFormatError extends CrmError {
2421
+ readonly status = 400;
2422
+ constructor(details: {
2423
+ email: string;
2424
+ reason: string;
2425
+ });
2426
+ }
2427
+ /**
2428
+ * An address handed to `LeadRepository.createLead` could not be canonicalised.
2429
+ *
2430
+ * 400, same shape and same reason as `SuppressionEmailFormatError`: the field is
2431
+ * malformed, no domain invariant was consulted. Thrown from
2432
+ * `core/email-identity.ts`, which owns the write-throws / setter-degrades
2433
+ * asymmetry and writes down why each side goes the way it does.
2434
+ */
2435
+ declare class LeadEmailFormatError extends CrmError {
2436
+ readonly status = 400;
2437
+ constructor(details: {
2438
+ email: string;
2439
+ reason: string;
2440
+ });
2441
+ }
2442
+ /** As {@link LeadEmailFormatError}, for `ContactRepository.createContact`. */
2443
+ declare class ContactEmailFormatError extends CrmError {
2444
+ readonly status = 400;
2445
+ constructor(details: {
2446
+ email: string;
2447
+ reason: string;
2448
+ });
2449
+ }
2450
+ //#endregion
2451
+ //#region src/core/email-identity.d.ts
2452
+ /**
2453
+ * WHERE the email key rule is applied inside CRM. **Not a second copy of the
2454
+ * rule** — the rule is `normalizeEmail` from `@classytic/contact/email`, imported
2455
+ * directly here as it is in `suppression.repository.ts`. What this module owns is
2456
+ * the two things a shared kernel cannot decide for us: which mongoose fields carry
2457
+ * the key, and what happens when the kernel refuses an input.
2458
+ *
2459
+ * It exists as a module rather than a copy-paste because Lead and Contact both
2460
+ * carry an email identity. Two schemas hand-rolling the same setter is the drift
2461
+ * this package already paid for once.
2462
+ *
2463
+ * ## Why the SETTER, and not only the repository verbs
2464
+ *
2465
+ * `LeadRepository.createLead` / `ContactRepository.createContact` are NOT on the
2466
+ * HTTP path. `@spinekit/crm` mounts both resources through arc's mongoose CRUD
2467
+ * adapter, so `POST /crm/leads` calls the inherited `Repository.create()` and the
2468
+ * verbs' pre-check never runs. A fix that lived only on the verbs would leave the
2469
+ * live route forking one mailbox into two rows, and the tests for the verbs would
2470
+ * be green while it did.
2471
+ *
2472
+ * The narrowest boundary EVERY write must cross is the schema field, so the key
2473
+ * rule is applied there. That is also what makes the partial-unique
2474
+ * `(organizationId, email)` index authoritative: an index can only dedup what the
2475
+ * key already folded, and `lowercase: true` does not IDNA-fold. `ada@bücher.de`
2476
+ * and `ada@xn--bcher-kva.de` are one host in two encodings — the index saw two
2477
+ * strings and admitted both, no error, one mailbox as two leads.
2478
+ *
2479
+ * Mongoose runs string setters on QUERY FILTERS as well as on writes (this is why
2480
+ * `lowercase: true` already made `getOne({ email: 'ADA@x.com' })` match a stored
2481
+ * `ada@x.com`), so the setter fixes the read side in the same stroke. The
2482
+ * repository verbs still canonicalise EXPLICITLY before querying — see
2483
+ * `requireLeadEmailKey`'s call sites. That is not redundancy for its own sake:
2484
+ * a pre-check that depends on an invisible schema option silently WIDENS the day
2485
+ * someone edits the field, and a widened filter does not fail, it matches nothing
2486
+ * and reports "no duplicate".
2487
+ *
2488
+ * ## The setter must NEVER throw, and it does not
2489
+ *
2490
+ * A throw inside a setter surfaces during query casting as an opaque `CastError`,
2491
+ * which would turn a malformed filter into a 500 on a READ. So the setter degrades
2492
+ * to the LEGACY folding (`trim().toLowerCase()`) for anything the kernel refuses —
2493
+ * exactly the behaviour those values have today, so nothing already stored is
2494
+ * re-keyed by a rule that cannot explain itself.
2495
+ *
2496
+ * Refusing is the WRITE VERBS' job, where a typed 400 can be thrown and the caller
2497
+ * is a deliberate domain call rather than a filter. Same asymmetry, same reason as
2498
+ * `suppression.repository.ts`: the safe direction differs per side.
2499
+ */
2500
+ /**
2501
+ * The canonical key for `email`, or `null` when it has none.
2502
+ *
2503
+ * Exported so a host can tell "not on file" apart from "not an address" without
2504
+ * re-deriving the rule — the same reason `suppressionEmailKey` is exported.
2505
+ */
2506
+ declare function crmEmailKey(email: string): string | null;
2137
2507
  //#endregion
2138
2508
  //#region src/shared/calculators/forecast.d.ts
2139
2509
  type OpportunityForecast = Pick<IOpportunityDocument, 'amount' | 'currency' | 'probability' | 'status' | 'expectedCloseAt' | 'stageId' | 'statusHistory'>;
@@ -2826,4 +3196,4 @@ declare namespace index_d_exports {
2826
3196
  export { AddNoteDto, CancelActivityDto, CloseLossDto, CloseWinDto, CompleteCampaignDto, ConfirmSubscriptionDto, ConvertLeadDto, CreateAccountDto, CreateCampaignDto, CreateContactDto, CreateLeadDto, CreateLossReasonDto, CreatePipelineDto, CreateSequenceInput, LeadFilterDto, MoveStageDto, OpenOpportunityDto, OpportunityFilterDto, PlanActivityDto, RecordEmailEventDto, SequenceFilterInput, StageInputDto, SubscribeDto, SuppressDto, UnsubscribeDto, UnsuppressDto, UpdateAccountDto, UpdateCampaignDto, UpdateContactDto, UpdateLeadDto, UpdateLossReasonDto, UpdateNoteDto, UpdatePipelineDto, UpdateSequenceInput, accountFilterSchema, activityFilterSchema, addNoteSchema, campaignFilterSchema, cancelActivitySchema, closeLossSchema, closeWinSchema, completeCampaignSchema, confirmSubscriptionSchema, contactFilterSchema, convertLeadSchema, createAccountSchema, createCampaignSchema, createContactSchema, createLeadSchema, createLossReasonSchema, createPipelineSchema, createSequenceSchema, currencyCodeSchema, emailEventFilterSchema, leadFilterSchema, lossReasonFilterSchema, metadataSchema, moneyAmountSchema, moveStageSchema, noteFilterSchema, objectIdLike, openOpportunitySchema, opportunityFilterSchema, pipelineFilterSchema, planActivitySchema, recordEmailEventSchema, sequenceFilterSchema, sequenceStepSchema, stageInputSchemaExport, subscribeSchema, subscriptionFilterSchema, suppressSchema, suppressionFilterSchema, tagsSchema, unsubscribeSchema, unsuppressSchema, updateAccountSchema, updateActivitySchema, updateCampaignSchema, updateContactSchema, updateLeadSchema, updateLossReasonSchema, updateNoteSchema, updatePipelineSchema, updateSequenceSchema };
2827
3197
  }
2828
3198
  //#endregion
2829
- export { ACTIVITY_MACHINE, ACTIVITY_STATUSES, ACTIVITY_TRANSITIONS, ACTIVITY_TYPES, AccountAssigned, AccountCreated, AccountEnriched, AccountMerged, AccountRepository, AccountRepositoryDeps, AccountUpdated, ActivityCancelled, ActivityCompleted, ActivityOutcomeRecorded, ActivityPlanned, ActivityRepository, ActivityRepositoryDeps, ActivityRescheduled, type ActivityStatus, type ActivityType, AlreadyEnrolledError, CAMPAIGN_MACHINE, CAMPAIGN_STATUSES, CAMPAIGN_TRANSITIONS, CONVERSATION_CHANNELS, CONVERSATION_PARTICIPANT_KINDS, CONVERSATION_STATUSES, CRM_EVENTS, CRM_MODEL_NAMES, CampaignActivated, CampaignArchived, CampaignCompleted, CampaignCreated, CampaignPaused, CampaignRepository, CampaignRepositoryDeps, CampaignResumed, type CampaignStatus, CloseInput, ContactAssigned, ContactCreated, ContactEnriched, ContactMerged, ContactRepository, ContactRepositoryDeps, ContactUpdated, ConversationAssigned, type ConversationChannel, ConversationCreated, type ConversationParticipantKind, ConversationReopened, ConversationRepository, ConversationRepositoryDeps, ConversationResolved, type ConversationStatus, ConversionRow, ConvertLeadInput, ConvertResult, CreateCrmRepositoriesDeps, CreateModelsOptions, CrmConfig, CrmContext, CrmEngine, CrmError, CrmEventDefinition, CrmEventDispatcher, type CrmEventDispatcherOptions, CrmEventName, CrmEventSchema, CrmLogger, CrmModels, CrmRepositories, DuplicateCampaignError, DuplicateLeadError, DuplicateSubscriptionError, EMAIL_EVENT_KINDS, EMAIL_EVENT_SUBJECT_KINDS, ENROLLMENT_MACHINE, ENROLLMENT_STATUSES, ENROLLMENT_SUBJECT_KINDS, ENROLLMENT_TRANSITIONS, EVENT_DERIVED_SUPPRESSION_REASONS, EmailEventBounced, EmailEventClicked, EmailEventComplained, type EmailEventKind, EmailEventOpened, EmailEventRepository, EmailEventRepositoryDeps, EmailEventSubjectKind, EmailEventUnsubscribed, EmptyPipelineError, EnrollInput, EnrollmentCompleted, EnrollmentCreated, EnrollmentExitReason, EnrollmentExited, EnrollmentPaused, EnrollmentRepository, EnrollmentRepositoryDeps, EnrollmentResumed, type EnrollmentStatus, EnrollmentStepSent, EnrollmentSubjectKind, ForecastOptions, ForecastRow, type IAccountDocument, type IActivityDocument, type ICampaignDocument, ICampaignStatusEntry, type IContactDocument, IContactInfoEmbedded, IContactName, type IConversationDocument, IDelayStep, type IEmailEventDocument, type IEnrollmentDocument, IEnrollmentStatusEntry, type ILeadDocument, ILeadEngagement, ILeadStatusEntry, type ILossReasonDocument, IMessageAttachment, type IMessageDocument, type INoteDocument, type IOpportunityDocument, IOpportunityStatusEntry, type IPipelineDocument, ISendEmailStep, type ISequenceDocument, ISequenceStatusEntry, IStageDocument, IStepResult, type ISubscriptionDocument, ISubscriptionStatusEntry, type ISuppressionDocument, InProcessCrmBus, type InternalCrmContext, InvalidActivityTransitionError, InvalidCampaignTransitionError, InvalidConfirmationTokenError, InvalidEnrollmentTransitionError, InvalidLeadTransitionError, InvalidOpportunityTransitionError, InvalidSequenceTransitionError, InvalidSubscriptionTransitionError, InvalidUnsubscribeTokenError, LEAD_MACHINE, LEAD_STATUSES, LEAD_TRANSITIONS, LeadAlreadyConvertedError, LeadAssigned, LeadConverted, LeadCreated, LeadDisqualified, LeadEngagementSignal, LeadEnriched, LeadInput, LeadRepository, LeadRepositoryDeps, LeadScored, LeadScorer, LeadService, LeadServiceDeps, type LeadStatus, LeadStatusChanged, LossReasonRepository, LossReasonRepositoryDeps, MESSAGE_DELIVERY_STATUSES, MESSAGE_DIRECTIONS, MergeAccountInput, MergeContactInput, type MessageDeliveryStatus, type MessageDirection, MessageReceived, MessageRepository, MessageRepositoryDeps, MessageSent, MoveStageInput, NotFoundError, NoteAdded, NoteRepository, NoteRepositoryDeps, OPPORTUNITY_MACHINE, OPPORTUNITY_STATUSES, OPPORTUNITY_TRANSITIONS, OpenConversationInput, OpenOpportunityInput, OpportunityAbandoned, OpportunityAmountChanged, OpportunityAssigned, OpportunityCreated, OpportunityExtended, OpportunityForecast, OpportunityLost, OpportunityRepository, OpportunityRepositoryDeps, OpportunityStageAdvanced, OpportunityStageRegressed, type OpportunityStatus, OpportunityWon, PENDING_EVENTS, PipelineArchived, PipelineCreated, PipelineRepository, PipelineRepositoryDeps, RecordEmailEventInput, RecordMessageInput, ReplySubject, ResolvedCrmConfig, type RunUnitConfig, SEQUENCE_MACHINE, SEQUENCE_STATUSES, SEQUENCE_TRANSITIONS, SUBJECT_KINDS, SUBSCRIPTION_MACHINE, SUBSCRIPTION_STATUSES, SUBSCRIPTION_TRANSITIONS, SUPPRESSION_REASONS, SchemaBuildConfig, SequenceActivated, SequenceArchived, SequenceCreated, SequencePaused, SequenceRepository, SequenceRepositoryDeps, type SequenceStatus, SequenceStep, StageNotInPipelineError, type StatusChangeEntry, type StatusHistory, StepResultStatus, type SubjectKind, SubscribeInput, SubscriptionBounced, SubscriptionComplained, SubscriptionConfirmed, SubscriptionCreated, SubscriptionRepository, SubscriptionRepositoryDeps, type SubscriptionStatus, SubscriptionUnsubscribed, SuppressInput, SuppressionAdded, type SuppressionReason, SuppressionRemoved, SuppressionRepository, SuppressionRepositoryDeps, TenantFieldType, UnmanagedSessionError, _ModelTypes, appendStatus, buildAccountSchema, buildActivitySchema, buildCampaignSchema, buildContactSchema, buildConversationSchema, buildEmailEventSchema, buildEnrollmentSchema, buildLeadSchema, buildLossReasonSchema, buildMessageSchema, buildNoteSchema, buildOpportunitySchema, buildPipelineSchema, buildSequenceSchema, buildSubscriptionSchema, buildSuppressionSchema, canTransitionActivity, canTransitionCampaign, canTransitionEnrollment, canTransitionLead, canTransitionOpportunity, canTransitionSequence, canTransitionSubscription, composeScorers, createAccountModel, createActivityModel, createCampaignModel, createContactModel, createConversationModel, createCrm, createCrmModels, createCrmRepositories, createEmailEventModel, createEvent, createLeadModel, createLossReasonModel, createMessageModel, createNoteModel, createOpportunityModel, createPipelineModel, createSubscriptionModel, crmEventDefinitions, defaultLeadScorer, flushPending, forecast, lastTransitionTo, latestEntry, pipelineConversion, runUnit, tenantFieldDef, timeInStatus, index_d_exports as validators };
3199
+ export { ACTIVITY_MACHINE, ACTIVITY_STATUSES, ACTIVITY_TRANSITIONS, ACTIVITY_TYPES, ALL_CRM_MODULES, AccountAssigned, AccountCreated, AccountEnriched, AccountMerged, AccountRepository, AccountRepositoryDeps, AccountUpdated, ActivityCancelled, ActivityCompleted, ActivityOutcomeRecorded, ActivityPlanned, ActivityRepository, ActivityRepositoryDeps, ActivityRescheduled, type ActivityStatus, type ActivityType, AlreadyEnrolledError, AutomationCrmModels, AutomationCrmRepositories, CAMPAIGN_MACHINE, CAMPAIGN_STATUSES, CAMPAIGN_TRANSITIONS, CONVERSATION_CHANNELS, CONVERSATION_PARTICIPANT_KINDS, CONVERSATION_STATUSES, CRM_EVENTS, CRM_MODEL_NAMES, CampaignActivated, CampaignArchived, CampaignCompleted, CampaignCreated, CampaignPaused, CampaignRepository, CampaignRepositoryDeps, CampaignResumed, type CampaignStatus, CloseInput, ContactAnonymized, ContactAssigned, ContactCreated, ContactEmailFormatError, ContactEnriched, ContactMergeRepoint, ContactMerged, ContactRepository, ContactRepositoryDeps, ContactUpdated, ConversationAssigned, type ConversationChannel, ConversationCreated, type ConversationParticipantKind, ConversationReopened, ConversationRepository, ConversationRepositoryDeps, ConversationResolved, type ConversationStatus, ConversionRow, ConvertLeadInput, ConvertResult, CreateCrmRepositoriesDeps, CrmBlueprint, CrmContext, CrmEngine, CrmEngineBase, CrmError, CrmEventDefinition, CrmEventDispatcher, type CrmEventDispatcherOptions, CrmEventName, CrmEventSchema, CrmLogger, CrmModels, CrmModuleView, CrmModulesConfig, CrmRepositories, CrmRuntime, CrmShape, DefineCrmModelsConfig, DuplicateCampaignError, DuplicateContactError, DuplicateLeadError, DuplicateSubscriptionError, EMAIL_EVENT_KINDS, EMAIL_EVENT_SUBJECT_KINDS, ENROLLMENT_MACHINE, ENROLLMENT_STATUSES, ENROLLMENT_SUBJECT_KINDS, ENROLLMENT_TRANSITIONS, EVENT_DERIVED_SUPPRESSION_REASONS, EmailEventBounced, EmailEventClicked, EmailEventComplained, type EmailEventKind, EmailEventOpened, EmailEventRepository, EmailEventRepositoryDeps, EmailEventSubjectKind, EmailEventUnsubscribed, EmptyPipelineError, EngagementCrmModels, EngagementCrmRepositories, EnrollInput, EnrollmentCompleted, EnrollmentCreated, EnrollmentExitReason, EnrollmentExited, EnrollmentPaused, EnrollmentRepository, EnrollmentRepositoryDeps, EnrollmentResumed, type EnrollmentStatus, EnrollmentStepSent, EnrollmentSubjectKind, ForecastOptions, ForecastRow, FullCrmModuleViews, IAccountDocument, IActivityDocument, ICampaignDocument, ICampaignStatusEntry, IContactDocument, IContactInfoEmbedded, IContactName, IConversationDocument, IDelayStep, IEmailEventDocument, IEnrollmentDocument, IEnrollmentStatusEntry, ILeadDocument, ILeadEngagement, ILeadStatusEntry, ILossReasonDocument, IMessageAttachment, IMessageDocument, INoteDocument, IOpportunityDocument, IOpportunityStatusEntry, IPipelineDocument, ISendEmailStep, ISequenceDocument, ISequenceStatusEntry, IStageDocument, IStepResult, ISubscriptionDocument, ISubscriptionStatusEntry, ISuppressionDocument, InProcessCrmBus, type InternalCrmContext, InvalidActivityTransitionError, InvalidCampaignTransitionError, InvalidConfirmationTokenError, InvalidEnrollmentTransitionError, InvalidLeadTransitionError, InvalidOpportunityTransitionError, InvalidSequenceTransitionError, InvalidSubscriptionTransitionError, InvalidUnsubscribeTokenError, LEAD_MACHINE, LEAD_STATUSES, LEAD_TRANSITIONS, LeadAlreadyConvertedError, LeadAnonymized, LeadAssigned, LeadConverted, LeadCreated, LeadDisqualified, LeadEmailFormatError, LeadEngagementSignal, LeadEnriched, LeadInput, LeadRepository, LeadRepositoryDeps, LeadScored, LeadScorer, LeadService, LeadServiceDeps, type LeadStatus, LeadStatusChanged, LossReasonRepository, LossReasonRepositoryDeps, MESSAGE_DELIVERY_STATUSES, MESSAGE_DIRECTIONS, MergeAccountInput, MergeContactInput, type MessageDeliveryStatus, type MessageDirection, MessageReceived, MessageRepository, MessageRepositoryDeps, MessageSent, ModularCrmEngine, ModularCrmModels, ModularCrmModuleViews, ModularCrmRepositories, MoveStageInput, NotFoundError, NoteAdded, NoteRepository, NoteRepositoryDeps, OPPORTUNITY_MACHINE, OPPORTUNITY_STATUSES, OPPORTUNITY_TRANSITIONS, OpenConversationInput, OpenOpportunityInput, OpportunityAbandoned, OpportunityAmountChanged, OpportunityAssigned, OpportunityCreated, OpportunityExtended, OpportunityForecast, OpportunityLost, OpportunityRepository, OpportunityRepositoryDeps, OpportunityStageAdvanced, OpportunityStageRegressed, type OpportunityStatus, OpportunityWon, PENDING_EVENTS, PipelineArchived, PipelineCreated, PipelineRepository, PipelineRepositoryDeps, RecordEmailEventInput, RecordMessageInput, ReplySubject, ResolvedCrmConfig, ResolvedCrmModules, type RunUnitConfig, SEQUENCE_MACHINE, SEQUENCE_STATUSES, SEQUENCE_TRANSITIONS, SUBJECT_KINDS, SUBSCRIPTION_MACHINE, SUBSCRIPTION_STATUSES, SUBSCRIPTION_TRANSITIONS, SUPPRESSION_REASONS, SalesCrmModels, SalesCrmRepositories, SchemaBuildConfig, SequenceActivated, SequenceArchived, SequenceCreated, SequencePaused, SequenceRepository, SequenceRepositoryDeps, type SequenceStatus, SequenceStep, StageNotInPipelineError, type StatusChangeEntry, type StatusHistory, StepResultStatus, type SubjectKind, SubscribeInput, SubscriptionBounced, SubscriptionComplained, SubscriptionConfirmed, SubscriptionCreated, SubscriptionRepository, SubscriptionRepositoryDeps, type SubscriptionStatus, SubscriptionUnsubscribed, SuppressInput, SuppressionAdded, SuppressionEmailFormatError, type SuppressionReason, SuppressionRemoved, SuppressionRepository, SuppressionRepositoryDeps, UnmanagedSessionError, _ModelTypes, appendStatus, buildAccountSchema, buildActivitySchema, buildCampaignSchema, buildContactSchema, buildConversationSchema, buildEmailEventSchema, buildEnrollmentSchema, buildLeadSchema, buildLossReasonSchema, buildMessageSchema, buildNoteSchema, buildOpportunitySchema, buildPipelineSchema, buildSequenceSchema, buildSubscriptionSchema, buildSuppressionSchema, canTransitionActivity, canTransitionCampaign, canTransitionEnrollment, canTransitionLead, canTransitionOpportunity, canTransitionSequence, canTransitionSubscription, composeScorers, createAccountModel, createActivityModel, createCampaignModel, createContactModel, createConversationModel, createCrmRepositories, createEmailEventModel, createEvent, createLeadModel, createLossReasonModel, createMessageModel, createNoteModel, createOpportunityModel, createPipelineModel, createSubscriptionModel, crmEmailKey, crmEventDefinitions, crmModelName, defaultLeadScorer, defineCrm, defineCrmModels, flushPending, forecast, lastTransitionTo, latestEntry, pipelineConversion, resolveCrmModules, runUnit, suppressionEmailKey, tenantFieldDef, timeInStatus, index_d_exports as validators };