@elevasis/core 0.13.0 → 0.14.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 (41) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +9 -2
  3. package/dist/organization-model/index.d.ts +1 -1
  4. package/dist/organization-model/index.js +9 -2
  5. package/dist/test-utils/index.d.ts +463 -377
  6. package/dist/test-utils/index.js +9 -2
  7. package/package.json +1 -1
  8. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +2324 -0
  9. package/src/business/acquisition/activity-events.test.ts +250 -0
  10. package/src/business/acquisition/activity-events.ts +7 -65
  11. package/src/business/acquisition/api-schemas.test.ts +1180 -0
  12. package/src/business/acquisition/api-schemas.ts +1075 -859
  13. package/src/business/acquisition/crm-state-actions.test.ts +160 -0
  14. package/src/business/acquisition/derive-actions.test.ts +518 -0
  15. package/src/business/acquisition/derive-actions.ts +103 -90
  16. package/src/business/acquisition/index.ts +149 -111
  17. package/src/business/acquisition/stateful.ts +30 -0
  18. package/src/business/acquisition/types.ts +44 -77
  19. package/src/execution/engine/index.ts +437 -434
  20. package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +363 -360
  21. package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.test.ts +162 -186
  22. package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.test.ts +316 -338
  23. package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-adapter.ts +204 -210
  24. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.test.ts +88 -0
  25. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.ts +141 -134
  26. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/utils/types.ts +76 -75
  27. package/src/execution/engine/tools/integration/service.test.ts +34 -9
  28. package/src/execution/engine/tools/integration/service.ts +6 -3
  29. package/src/execution/engine/tools/lead-service-types.ts +945 -888
  30. package/src/execution/engine/tools/platform/acquisition/types.ts +266 -260
  31. package/src/execution/engine/tools/registry.ts +701 -699
  32. package/src/execution/engine/tools/tool-maps.ts +816 -791
  33. package/src/execution/engine/workflow/types.ts +11 -0
  34. package/src/organization-model/contracts.ts +4 -4
  35. package/src/organization-model/domains/navigation.ts +62 -62
  36. package/src/organization-model/domains/sales.ts +272 -0
  37. package/src/organization-model/published.ts +21 -21
  38. package/src/organization-model/resolve.ts +21 -8
  39. package/src/platform/constants/versions.ts +1 -1
  40. package/src/reference/_generated/contracts.md +2324 -0
  41. package/src/supabase/database.types.ts +2958 -2886
@@ -803,3 +803,2327 @@ export interface DeploymentSpec {
803
803
  humanCheckpoints?: HumanCheckpointDefinition[]
804
804
  }
805
805
  ```
806
+
807
+ ## CRM Platform Primitives
808
+
809
+ ### `AcqCompany`
810
+
811
+ ```typescript
812
+ /**
813
+ * Company record in the acquisition database.
814
+ * Contains enriched company data from various sources.
815
+ * Transformed from AcqCompanyRow with camelCase properties.
816
+ */
817
+ export interface AcqCompany {
818
+ id: string
819
+ organizationId: string
820
+ name: string
821
+ domain: string | null
822
+ linkedinUrl: string | null
823
+ website: string | null
824
+ numEmployees: number | null
825
+ foundedYear: number | null
826
+ locationCity: string | null
827
+ locationState: string | null
828
+ category: string | null
829
+ categoryPain: string | null
830
+ segment: string | null
831
+ pipelineStatus: CompanyPipelineStatus | null
832
+ enrichmentData: CompanyEnrichmentData | null
833
+ source: string | null
834
+ batchId: string | null
835
+ status: 'active' | 'invalid'
836
+ verticalResearch: string | null
837
+ /** Track A: flat qualification score (null until a scoring rubric is defined). Added by W1 migration. */
838
+ qualificationScore: number | null
839
+ /** Track A: flat qualification signals jsonb preserving the result payload shape. Added by W1 migration. */
840
+ qualificationSignals: Record<string, unknown> | null
841
+ /** Track A: key identifying the rubric used for qualification. Added by W1 migration. */
842
+ qualificationRubricKey: string | null
843
+ createdAt: Date
844
+ updatedAt: Date
845
+ }
846
+ ```
847
+
848
+ ### `AcqContact`
849
+
850
+ ```typescript
851
+ /**
852
+ * Contact record in the acquisition database.
853
+ * Contains enriched contact data and personalization content.
854
+ * Transformed from AcqContactRow with camelCase properties.
855
+ */
856
+ export interface AcqContact {
857
+ id: string
858
+ organizationId: string
859
+ companyId: string | null
860
+ email: string
861
+ emailValid: 'VALID' | 'INVALID' | 'RISKY' | 'UNKNOWN' | null
862
+ firstName: string | null
863
+ lastName: string | null
864
+ linkedinUrl: string | null
865
+ title: string | null
866
+ headline: string | null
867
+ filterReason: string | null
868
+ openingLine: string | null
869
+ source: string | null
870
+ sourceId: string | null
871
+ pipelineStatus: ContactPipelineStatus | null
872
+ enrichmentData: ContactEnrichmentData | null
873
+ /** Attio Person record ID - set when contact responds and is added to CRM */
874
+ attioPersonId: string | null
875
+ batchId: string | null
876
+ status: 'active' | 'invalid'
877
+ createdAt: Date
878
+ updatedAt: Date
879
+ }
880
+ ```
881
+
882
+ ### `DealStage`
883
+
884
+ ```typescript
885
+ export type DealStage = 'interested' | 'proposal' | 'closing' | 'closed_won' | 'closed_lost' | 'nurturing'
886
+ ```
887
+
888
+ ### `KanbanStageConfig`
889
+
890
+ ```typescript
891
+ export interface KanbanStageConfig {
892
+ color: string // Mantine color token (e.g. 'blue', 'teal')
893
+ label?: string // Optional display label override
894
+ }
895
+ ```
896
+
897
+ ### `KanbanBoardConfig`
898
+
899
+ ```typescript
900
+ export type KanbanBoardConfig = Partial<Record<DealStage, KanbanStageConfig>>
901
+ ```
902
+
903
+ ### `DealContact`
904
+
905
+ ```typescript
906
+ export interface DealContact {
907
+ id: string
908
+ first_name: string | null
909
+ last_name: string | null
910
+ email: string
911
+ title: string | null
912
+ headline: string | null
913
+ linkedin_url: string | null
914
+ pipeline_status: Record<string, unknown> | null
915
+ enrichment_data: Record<string, unknown> | null
916
+ company: {
917
+ id: string
918
+ name: string
919
+ domain: string | null
920
+ website: string | null
921
+ linkedin_url: string | null
922
+ segment: string | null
923
+ category: string | null
924
+ num_employees: number | null
925
+ } | null
926
+ }
927
+ ```
928
+
929
+ ### `DealFilters`
930
+
931
+ ```typescript
932
+ export interface DealFilters {
933
+ stage?: DealStage
934
+ search?: string
935
+ limit?: number
936
+ offset?: number
937
+ }
938
+ ```
939
+
940
+ ### `DealListItem`
941
+
942
+ ```typescript
943
+ /** Deal list item with joined contact and company data */
944
+ export interface DealListItem extends AcqDealRow {
945
+ contact: DealContact | null
946
+ }
947
+ ```
948
+
949
+ ### `DealDetail`
950
+
951
+ ```typescript
952
+ export type DealDetail = DealListItem
953
+ ```
954
+
955
+ ### `AcqDealTaskKind`
956
+
957
+ ```typescript
958
+ /** Task kind options for a deal task (human follow-up action type) */
959
+ export type AcqDealTaskKind = 'call' | 'email' | 'meeting' | 'other'
960
+ ```
961
+
962
+ ### `AcqDealTask`
963
+
964
+ ```typescript
965
+ /**
966
+ * A CRM to-do item attached to a deal representing a human follow-up action.
967
+ * Transformed from AcqDealTaskRow with camelCase properties.
968
+ */
969
+ export interface AcqDealTask {
970
+ id: string
971
+ organizationId: string
972
+ dealId: string
973
+ title: string
974
+ description: string | null
975
+ kind: AcqDealTaskKind
976
+ dueAt: string | null
977
+ assigneeUserId: string | null
978
+ completedAt: string | null
979
+ completedByUserId: string | null
980
+ createdAt: string
981
+ updatedAt: string
982
+ createdByUserId: string | null
983
+ }
984
+ ```
985
+
986
+ ### `DealStageSchema`
987
+
988
+ ```typescript
989
+ export const DealStageSchema = z.enum(['interested', 'proposal', 'closing', 'closed_won', 'closed_lost', 'nurturing'])
990
+ ```
991
+
992
+ ### `AcqDealTaskKindSchema`
993
+
994
+ ```typescript
995
+ export const AcqDealTaskKindSchema = z.enum(['call', 'email', 'meeting', 'other'])
996
+ ```
997
+
998
+ ### `DealIdParamsSchema`
999
+
1000
+ ```typescript
1001
+ export const DealIdParamsSchema = z.object({
1002
+ dealId: UuidSchema
1003
+ })
1004
+ ```
1005
+
1006
+ ### `DealTaskIdParamsSchema`
1007
+
1008
+ ```typescript
1009
+ export const DealTaskIdParamsSchema = z.object({
1010
+ dealId: UuidSchema,
1011
+ taskId: UuidSchema
1012
+ })
1013
+ ```
1014
+
1015
+ ### `ListDealsQuerySchema`
1016
+
1017
+ ```typescript
1018
+ export const ListDealsQuerySchema = z
1019
+ .object({
1020
+ stage: DealStageSchema.optional(),
1021
+ search: z.string().optional(),
1022
+ limit: z.coerce.number().int().positive().default(50),
1023
+ offset: z.coerce.number().int().min(0).default(0)
1024
+ })
1025
+ .strict()
1026
+ ```
1027
+
1028
+ ### `DealLookupQuerySchema`
1029
+
1030
+ ```typescript
1031
+ export const DealLookupQuerySchema = z
1032
+ .object({
1033
+ search: z.string().trim().min(1).max(200).optional(),
1034
+ limit: z.coerce.number().int().min(1).max(25).default(10)
1035
+ })
1036
+ .strict()
1037
+ ```
1038
+
1039
+ ### `ListDealTasksDueQuerySchema`
1040
+
1041
+ ```typescript
1042
+ export const ListDealTasksDueQuerySchema = z
1043
+ .object({
1044
+ window: z.enum(['overdue', 'today', 'today_and_overdue', 'upcoming']).optional(),
1045
+ assigneeUserId: UuidSchema.optional()
1046
+ })
1047
+ .strict()
1048
+ ```
1049
+
1050
+ ### `CreateDealNoteRequestSchema`
1051
+
1052
+ ```typescript
1053
+ export const CreateDealNoteRequestSchema = z
1054
+ .object({
1055
+ body: z.string().trim().min(1).max(10000)
1056
+ })
1057
+ .strict()
1058
+ ```
1059
+
1060
+ ### `CreateDealTaskRequestSchema`
1061
+
1062
+ ```typescript
1063
+ export const CreateDealTaskRequestSchema = z
1064
+ .object({
1065
+ title: z.string().trim().min(1).max(255),
1066
+ description: z.string().nullable().optional(),
1067
+ kind: AcqDealTaskKindSchema.optional(),
1068
+ dueAt: z.string().datetime().nullable().optional(),
1069
+ assigneeUserId: UuidSchema.nullable().optional()
1070
+ })
1071
+ .strict()
1072
+ ```
1073
+
1074
+ ### `TransitionItemRequestSchema`
1075
+
1076
+ ```typescript
1077
+ export const TransitionItemRequestSchema = z
1078
+ .object({
1079
+ pipelineKey: z.string().min(1),
1080
+ stageKey: z.string().min(1),
1081
+ stateKey: z.string().nullable().optional(),
1082
+ reason: z.string().optional(),
1083
+ expectedUpdatedAt: z.string().datetime().optional()
1084
+ })
1085
+ .strict()
1086
+ ```
1087
+
1088
+ ### `ExecuteActionParamsSchema`
1089
+
1090
+ ```typescript
1091
+ export const ExecuteActionParamsSchema = z
1092
+ .object({
1093
+ dealId: UuidSchema,
1094
+ actionKey: NonEmptyStringSchema
1095
+ })
1096
+ .strict()
1097
+ ```
1098
+
1099
+ ### `ExecuteActionRequestSchema`
1100
+
1101
+ ```typescript
1102
+ export const ExecuteActionRequestSchema = z
1103
+ .object({
1104
+ payload: z.record(z.string(), z.unknown()).optional()
1105
+ })
1106
+ .strict()
1107
+ ```
1108
+
1109
+ ### `DealContactSummarySchema`
1110
+
1111
+ ```typescript
1112
+ /**
1113
+ * Contact summary nested inside DealListItem / DealDetailResponse.
1114
+ * Matches the joined shape returned by useDeals / useDealDetail Supabase queries.
1115
+ */
1116
+ export const DealContactSummarySchema = z.object({
1117
+ id: z.string(),
1118
+ first_name: z.string().nullable(),
1119
+ last_name: z.string().nullable(),
1120
+ email: z.string(),
1121
+ title: z.string().nullable(),
1122
+ headline: z.string().nullable(),
1123
+ linkedin_url: z.string().nullable(),
1124
+ pipeline_status: z.record(z.string(), z.unknown()).nullable(),
1125
+ enrichment_data: z.record(z.string(), z.unknown()).nullable(),
1126
+ company: z
1127
+ .object({
1128
+ id: z.string(),
1129
+ name: z.string(),
1130
+ domain: z.string().nullable(),
1131
+ website: z.string().nullable(),
1132
+ linkedin_url: z.string().nullable(),
1133
+ segment: z.string().nullable(),
1134
+ category: z.string().nullable(),
1135
+ num_employees: z.number().nullable()
1136
+ })
1137
+ .nullable()
1138
+ })
1139
+ ```
1140
+
1141
+ ### `DealListItemSchema`
1142
+
1143
+ ```typescript
1144
+ /**
1145
+ * Deal list item with joined contact (and company via contact).
1146
+ * Matches DealListItem from @repo/core types.
1147
+ */
1148
+ export const DealListItemSchema = z.object({
1149
+ // acq_deals columns
1150
+ id: z.string(),
1151
+ organization_id: z.string(),
1152
+ contact_id: z.string().nullable(),
1153
+ contact_email: z.string(),
1154
+ pipeline_key: z.string(),
1155
+ stage_key: z.string().nullable(),
1156
+ state_key: z.string().nullable(),
1157
+ activity_log: z.unknown(),
1158
+ discovery_data: z.unknown().nullable(),
1159
+ discovery_submitted_at: z.string().nullable(),
1160
+ discovery_submitted_by: z.string().nullable(),
1161
+ proposal_data: z.unknown().nullable(),
1162
+ proposal_sent_at: z.string().nullable(),
1163
+ proposal_pdf_url: z.string().nullable(),
1164
+ signature_envelope_id: z.string().nullable(),
1165
+ source_list_id: z.string().nullable(),
1166
+ source_type: z.string().nullable(),
1167
+ initial_fee: z.number().nullable(),
1168
+ monthly_fee: z.number().nullable(),
1169
+ closed_lost_at: z.string().nullable(),
1170
+ closed_lost_reason: z.string().nullable(),
1171
+ created_at: z.string(),
1172
+ updated_at: z.string(),
1173
+ // joined relation
1174
+ contact: DealContactSummarySchema.nullable()
1175
+ })
1176
+ ```
1177
+
1178
+ ### `DealListResponseSchema`
1179
+
1180
+ ```typescript
1181
+ export const DealListResponseSchema = z.object({
1182
+ data: z.array(DealListItemSchema),
1183
+ total: z.number().int(),
1184
+ limit: z.number().int(),
1185
+ offset: z.number().int()
1186
+ })
1187
+ ```
1188
+
1189
+ ### `DealDetailResponseSchema`
1190
+
1191
+ ```typescript
1192
+ /**
1193
+ * Deal detail shape — currently the same as a list item (full joined record).
1194
+ * useDealDetail returns DealDetail which is typed as DealListItem.
1195
+ */
1196
+ export const DealDetailResponseSchema = DealListItemSchema
1197
+ ```
1198
+
1199
+ ### `DealNoteResponseSchema`
1200
+
1201
+ ```typescript
1202
+ /**
1203
+ * Single acq_deal_notes row (camelCase API representation).
1204
+ */
1205
+ export const DealNoteResponseSchema = z.object({
1206
+ id: z.string(),
1207
+ dealId: z.string(),
1208
+ organizationId: z.string(),
1209
+ authorUserId: z.string().nullable(),
1210
+ body: z.string(),
1211
+ createdAt: z.string(),
1212
+ updatedAt: z.string()
1213
+ })
1214
+ ```
1215
+
1216
+ ### `DealNoteListResponseSchema`
1217
+
1218
+ ```typescript
1219
+ export const DealNoteListResponseSchema = z.array(DealNoteResponseSchema)
1220
+ ```
1221
+
1222
+ ### `DealTaskResponseSchema`
1223
+
1224
+ ```typescript
1225
+ /**
1226
+ * Single acq_deal_tasks row (camelCase API representation).
1227
+ * Matches AcqDealTask domain type from types.ts.
1228
+ */
1229
+ export const DealTaskResponseSchema = z.object({
1230
+ id: z.string(),
1231
+ organizationId: z.string(),
1232
+ dealId: z.string(),
1233
+ title: z.string(),
1234
+ description: z.string().nullable(),
1235
+ kind: AcqDealTaskKindSchema,
1236
+ dueAt: z.string().nullable(),
1237
+ assigneeUserId: z.string().nullable(),
1238
+ completedAt: z.string().nullable(),
1239
+ completedByUserId: z.string().nullable(),
1240
+ createdAt: z.string(),
1241
+ updatedAt: z.string(),
1242
+ createdByUserId: z.string().nullable()
1243
+ })
1244
+ ```
1245
+
1246
+ ### `DealTaskListResponseSchema`
1247
+
1248
+ ```typescript
1249
+ export const DealTaskListResponseSchema = z.array(DealTaskResponseSchema)
1250
+ ```
1251
+
1252
+ ### `DealSchemas`
1253
+
1254
+ ```typescript
1255
+ export const DealSchemas = {
1256
+ // Params
1257
+ DealIdParams: DealIdParamsSchema,
1258
+ DealTaskIdParams: DealTaskIdParamsSchema,
1259
+
1260
+ // Queries
1261
+ ListDealsQuery: ListDealsQuerySchema,
1262
+ DealLookupQuery: DealLookupQuerySchema,
1263
+ ListDealTasksDueQuery: ListDealTasksDueQuerySchema,
1264
+
1265
+ // Request bodies
1266
+ CreateDealNoteRequest: CreateDealNoteRequestSchema,
1267
+ CreateDealTaskRequest: CreateDealTaskRequestSchema,
1268
+ TransitionItemRequest: TransitionItemRequestSchema,
1269
+ ExecuteActionParams: ExecuteActionParamsSchema,
1270
+ ExecuteActionRequest: ExecuteActionRequestSchema,
1271
+
1272
+ // Responses
1273
+ DealListResponse: DealListResponseSchema,
1274
+ DealSummaryResponse: DealSummaryResponseSchema,
1275
+ DealLookupResponse: DealLookupResponseSchema,
1276
+ DealDetailResponse: DealDetailResponseSchema,
1277
+ DealNoteResponse: DealNoteResponseSchema,
1278
+ DealNoteListResponse: DealNoteListResponseSchema,
1279
+ DealTaskResponse: DealTaskResponseSchema,
1280
+ DealTaskListResponse: DealTaskListResponseSchema
1281
+ }
1282
+ ```
1283
+
1284
+ ### `Action`
1285
+
1286
+ ```typescript
1287
+ export interface Action {
1288
+ key: string
1289
+ label: string
1290
+ payloadSchema?: z.ZodTypeAny
1291
+ }
1292
+ ```
1293
+
1294
+ ### `ActionDef`
1295
+
1296
+ ```typescript
1297
+ export interface ActionDef {
1298
+ key: string
1299
+ label: string
1300
+ isAvailableFor: (deal: AcqDealRow) => boolean
1301
+ workflowId: string
1302
+ payloadSchema?: z.ZodTypeAny
1303
+ }
1304
+ ```
1305
+
1306
+ ### `DEFAULT_CRM_ACTIONS`
1307
+
1308
+ ```typescript
1309
+ export const DEFAULT_CRM_ACTIONS: ActionDef[] = [
1310
+ {
1311
+ key: 'move_to_proposal',
1312
+ label: 'Move to Proposal',
1313
+ isAvailableFor: (deal) => deal.stage_key === 'interested',
1314
+ workflowId: 'move_to_proposal-workflow'
1315
+ },
1316
+ {
1317
+ key: 'move_to_closing',
1318
+ label: 'Move to Closing',
1319
+ isAvailableFor: (deal) => deal.stage_key === 'proposal',
1320
+ workflowId: 'move_to_closing-workflow'
1321
+ },
1322
+ {
1323
+ key: 'move_to_closed_won',
1324
+ label: 'Close Won',
1325
+ isAvailableFor: (deal) => deal.stage_key === 'closing',
1326
+ workflowId: 'move_to_closed_won-workflow'
1327
+ },
1328
+ {
1329
+ key: 'move_to_closed_lost',
1330
+ label: 'Close Lost',
1331
+ isAvailableFor: (deal) =>
1332
+ deal.stage_key === 'interested' || deal.stage_key === 'proposal' || deal.stage_key === 'closing',
1333
+ workflowId: 'move_to_closed_lost-workflow'
1334
+ },
1335
+ {
1336
+ key: 'move_to_nurturing',
1337
+ label: 'Move to Nurturing',
1338
+ isAvailableFor: (deal) =>
1339
+ deal.stage_key === 'interested' || deal.stage_key === 'proposal' || deal.stage_key === 'closing',
1340
+ workflowId: 'move_to_nurturing-workflow'
1341
+ },
1342
+ {
1343
+ key: 'send_reply',
1344
+ label: 'Send Reply',
1345
+ isAvailableFor: (deal) =>
1346
+ deal.stage_key === 'interested' &&
1347
+ (deal.state_key === 'discovery_replied' ||
1348
+ deal.state_key === 'discovery_link_sent' ||
1349
+ deal.state_key === 'discovery_nudging'),
1350
+ workflowId: 'crm-send-reply-workflow',
1351
+ payloadSchema: SendReplyActionPayloadSchema
1352
+ },
1353
+ {
1354
+ key: 'send_link',
1355
+ label: 'Send Booking Link',
1356
+ isAvailableFor: (deal) => deal.stage_key === 'interested' && deal.state_key === 'discovery_replied',
1357
+ workflowId: 'crm-send-booking-link-workflow'
1358
+ },
1359
+ {
1360
+ key: 'send_nudge',
1361
+ label: 'Send Nudge',
1362
+ isAvailableFor: (deal) =>
1363
+ deal.stage_key === 'interested' &&
1364
+ (deal.state_key === 'discovery_link_sent' || deal.state_key === 'discovery_nudging'),
1365
+ workflowId: 'crm-send-nudge-workflow'
1366
+ },
1367
+ {
1368
+ key: 'mark_no_show',
1369
+ label: 'Mark No-Show',
1370
+ isAvailableFor: (deal) => deal.stage_key === 'interested' && deal.state_key === 'discovery_nudging',
1371
+ // Mirrors the auto-timeout precedent in operations/sales/crm/pipeline/timeout-actions.ts:
1372
+ // both manual-click and timeout move the deal to closed_lost. The action_taken activity
1373
+ // event captures operator intent and distinguishes the manual variant from the timed one.
1374
+ workflowId: 'mark_no_show-workflow'
1375
+ },
1376
+ {
1377
+ key: 'rebook',
1378
+ label: 'Rebook',
1379
+ isAvailableFor: (deal) => deal.stage_key === 'interested' && deal.state_key === 'discovery_booking_cancelled',
1380
+ workflowId: 'crm-rebook-workflow'
1381
+ }
1382
+ ]
1383
+ ```
1384
+
1385
+ ### `CrmToolMap`
1386
+
1387
+ ```typescript
1388
+ export type CrmToolMap = {
1389
+ getRecentActivity: { params: CrmRecentActivityParams; result: RecentActivityEntry[] }
1390
+ listDeals: { params: CrmListDealsParams; result: DealListItem[] }
1391
+ getDeal: { params: CrmGetDealParams; result: DealDetail | null }
1392
+ getDealByEmail: { params: CrmGetDealByEmailParams; result: DealDetail | null }
1393
+ createDealNote: { params: CrmDealNoteParams; result: AcqDealNote }
1394
+ listDealNotes: { params: Omit<ListDealNotesParams, 'organizationId'>; result: AcqDealNote[] }
1395
+ createDealTask: { params: CrmDealTaskParams; result: AcqDealTask }
1396
+ listDealTasks: { params: Omit<ListDealTasksParams, 'organizationId'>; result: AcqDealTask[] }
1397
+ listDealTasksDue: { params: CrmTaskDueParams; result: AcqDealTask[] }
1398
+ completeDealTask: { params: Omit<CompleteDealTaskParams, 'organizationId'>; result: AcqDealTask }
1399
+ recordActivity: { params: CrmRecordActivityParams; result: void }
1400
+ deleteDeal: { params: CrmDeleteDealParams; result: void }
1401
+ }
1402
+ ```
1403
+
1404
+ ## Lead Gen Platform Primitives
1405
+
1406
+ ### `WebPost`
1407
+
1408
+ ```typescript
1409
+ /**
1410
+ * Represents a web post from company website scraping.
1411
+ * Used for recent blog posts, news, or announcements.
1412
+ */
1413
+ export interface WebPost {
1414
+ /** ISO date string of when the post was published */
1415
+ date: string
1416
+ /** Title of the web post */
1417
+ title: string
1418
+ /** Brief summary of the post content */
1419
+ summary: string
1420
+ /** AI-generated insight about the post's relevance */
1421
+ aiInsight?: string
1422
+ }
1423
+ ```
1424
+
1425
+ ### `CompanyPipelineStatus`
1426
+
1427
+ ```typescript
1428
+ /**
1429
+ * Tracks pipeline status for a company across all processing stages.
1430
+ */
1431
+ export interface CompanyPipelineStatus {
1432
+ acquired: boolean
1433
+ enrichment: {
1434
+ [source: string]: {
1435
+ status: 'pending' | 'complete' | 'failed' | 'skipped'
1436
+ completedAt?: string
1437
+ error?: string
1438
+ }
1439
+ }
1440
+ }
1441
+ ```
1442
+
1443
+ ### `ContactPipelineStatus`
1444
+
1445
+ ```typescript
1446
+ /**
1447
+ * Tracks pipeline status for a contact across all processing stages.
1448
+ */
1449
+ export interface ContactPipelineStatus {
1450
+ enrichment: {
1451
+ [source: string]: {
1452
+ status: 'pending' | 'complete' | 'failed' | 'skipped'
1453
+ completedAt?: string
1454
+ error?: string
1455
+ }
1456
+ }
1457
+ personalization: {
1458
+ status: 'pending' | 'complete' | 'failed' | 'skipped'
1459
+ completedAt?: string
1460
+ }
1461
+ outreach: {
1462
+ status: 'pending' | 'sent' | 'replied' | 'bounced' | 'opted-out'
1463
+ sentAt?: string
1464
+ channel?: string
1465
+ campaignId?: string
1466
+ }
1467
+ }
1468
+ ```
1469
+
1470
+ ### `CompanyEnrichmentData`
1471
+
1472
+ ```typescript
1473
+ /**
1474
+ * Enrichment data collected for a company from various sources.
1475
+ */
1476
+ export interface CompanyEnrichmentData {
1477
+ googleMaps?: {
1478
+ placeId?: string
1479
+ totalScore?: number
1480
+ reviewsCount?: number
1481
+ address?: string
1482
+ phone?: string
1483
+ categoryName?: string
1484
+ googleMapsUrl?: string
1485
+ scrapedAt?: string
1486
+ }
1487
+ websiteCrawl?: {
1488
+ companyDescription?: string
1489
+ services?: string[]
1490
+ specialties?: string[]
1491
+ staff?: Array<{ name: string; title?: string; email?: string }>
1492
+ automationGaps?: string[]
1493
+ targetAudience?: string
1494
+ category?: string
1495
+ segment?: string
1496
+ recentWin?: string
1497
+ emailCount?: number
1498
+ pageCount?: number
1499
+ totalChars?: number
1500
+ crawledAt?: string
1501
+ extractedAt?: string
1502
+ }
1503
+ website?: {
1504
+ missionVision?: string
1505
+ uniqueAttributes?: string
1506
+ coreOfferings?: string
1507
+ targetAudience?: string
1508
+ companyValues?: string
1509
+ businessDescription?: string
1510
+ recentPosts?: Array<{ date?: string; title?: string; summary?: string; aiInsight?: string }>
1511
+ }
1512
+ tomba?: {
1513
+ waterfallEmail?: {
1514
+ email: string
1515
+ name?: string
1516
+ title?: string
1517
+ department?: string
1518
+ } | null
1519
+ genericEmail?: string | null
1520
+ totalFound?: number
1521
+ searchedAt?: string
1522
+ }
1523
+ }
1524
+ ```
1525
+
1526
+ ### `ContactEnrichmentData`
1527
+
1528
+ ```typescript
1529
+ /**
1530
+ * Enrichment data collected for a contact from various sources.
1531
+ */
1532
+ export interface ContactEnrichmentData {
1533
+ linkedin?: {
1534
+ summary?: string
1535
+ pastExperience?: string
1536
+ education?: string
1537
+ activity?: Array<{ date?: string; content?: string }>
1538
+ }
1539
+ }
1540
+ ```
1541
+
1542
+ ### `AcqList`
1543
+
1544
+ ```typescript
1545
+ export interface AcqList {
1546
+ id: string
1547
+ organizationId: string
1548
+ name: string
1549
+ description: string | null
1550
+ batchIds: string[]
1551
+ instantlyCampaignId: string | null
1552
+ status: ListStatus
1553
+ scrapingConfig: ScrapingConfig
1554
+ icp: IcpRubric
1555
+ pipelineConfig: PipelineConfig
1556
+ metadata: Record<string, unknown>
1557
+ launchedAt: Date | null
1558
+ completedAt: Date | null
1559
+ createdAt: Date
1560
+ }
1561
+ ```
1562
+
1563
+ ### `AcqCompany`
1564
+
1565
+ ```typescript
1566
+ /**
1567
+ * Company record in the acquisition database.
1568
+ * Contains enriched company data from various sources.
1569
+ * Transformed from AcqCompanyRow with camelCase properties.
1570
+ */
1571
+ export interface AcqCompany {
1572
+ id: string
1573
+ organizationId: string
1574
+ name: string
1575
+ domain: string | null
1576
+ linkedinUrl: string | null
1577
+ website: string | null
1578
+ numEmployees: number | null
1579
+ foundedYear: number | null
1580
+ locationCity: string | null
1581
+ locationState: string | null
1582
+ category: string | null
1583
+ categoryPain: string | null
1584
+ segment: string | null
1585
+ pipelineStatus: CompanyPipelineStatus | null
1586
+ enrichmentData: CompanyEnrichmentData | null
1587
+ source: string | null
1588
+ batchId: string | null
1589
+ status: 'active' | 'invalid'
1590
+ verticalResearch: string | null
1591
+ /** Track A: flat qualification score (null until a scoring rubric is defined). Added by W1 migration. */
1592
+ qualificationScore: number | null
1593
+ /** Track A: flat qualification signals jsonb preserving the result payload shape. Added by W1 migration. */
1594
+ qualificationSignals: Record<string, unknown> | null
1595
+ /** Track A: key identifying the rubric used for qualification. Added by W1 migration. */
1596
+ qualificationRubricKey: string | null
1597
+ createdAt: Date
1598
+ updatedAt: Date
1599
+ }
1600
+ ```
1601
+
1602
+ ### `AcqContact`
1603
+
1604
+ ```typescript
1605
+ /**
1606
+ * Contact record in the acquisition database.
1607
+ * Contains enriched contact data and personalization content.
1608
+ * Transformed from AcqContactRow with camelCase properties.
1609
+ */
1610
+ export interface AcqContact {
1611
+ id: string
1612
+ organizationId: string
1613
+ companyId: string | null
1614
+ email: string
1615
+ emailValid: 'VALID' | 'INVALID' | 'RISKY' | 'UNKNOWN' | null
1616
+ firstName: string | null
1617
+ lastName: string | null
1618
+ linkedinUrl: string | null
1619
+ title: string | null
1620
+ headline: string | null
1621
+ filterReason: string | null
1622
+ openingLine: string | null
1623
+ source: string | null
1624
+ sourceId: string | null
1625
+ pipelineStatus: ContactPipelineStatus | null
1626
+ enrichmentData: ContactEnrichmentData | null
1627
+ /** Attio Person record ID - set when contact responds and is added to CRM */
1628
+ attioPersonId: string | null
1629
+ batchId: string | null
1630
+ status: 'active' | 'invalid'
1631
+ createdAt: Date
1632
+ updatedAt: Date
1633
+ }
1634
+ ```
1635
+
1636
+ ### `ListTelemetry`
1637
+
1638
+ ```typescript
1639
+ /**
1640
+ * Live-scan aggregate telemetry for a single list, computed on demand from
1641
+ * the list junction tables and current contact deliverability state.
1642
+ */
1643
+ export interface ListTelemetry {
1644
+ listId: string
1645
+ totalCompanies: number
1646
+ totalContacts: number
1647
+ stageCounts: {
1648
+ populated: number
1649
+ extracted: number
1650
+ qualified: number
1651
+ discovered: number
1652
+ verified: number
1653
+ personalized: number
1654
+ uploaded: number
1655
+ }
1656
+ deliverability: {
1657
+ valid: number
1658
+ risky: number
1659
+ invalid: number
1660
+ unknown: number
1661
+ bounced: number
1662
+ }
1663
+ /** Reserved -- active workflow IDs associated with this list. */
1664
+ activeWorkflows?: string[]
1665
+ }
1666
+ ```
1667
+
1668
+ ### `ListStageCountsSchema`
1669
+
1670
+ ```typescript
1671
+ export const ListStageCountsSchema = z.object({
1672
+ stageCounts: z.object({
1673
+ populated: z.number().int(),
1674
+ extracted: z.number().int(),
1675
+ qualified: z.number().int(),
1676
+ discovered: z.number().int(),
1677
+ verified: z.number().int(),
1678
+ personalized: z.number().int(),
1679
+ uploaded: z.number().int()
1680
+ }),
1681
+ deliverability: z.object({
1682
+ valid: z.number().int(),
1683
+ risky: z.number().int(),
1684
+ invalid: z.number().int(),
1685
+ unknown: z.number().int(),
1686
+ bounced: z.number().int()
1687
+ })
1688
+ })
1689
+ ```
1690
+
1691
+ ### `ListTelemetrySchema`
1692
+
1693
+ ```typescript
1694
+ export const ListTelemetrySchema = z.object({
1695
+ listId: UuidSchema,
1696
+ totalCompanies: z.number().int(),
1697
+ totalContacts: z.number().int(),
1698
+ stageCounts: ListStageCountsSchema.shape.stageCounts,
1699
+ deliverability: ListStageCountsSchema.shape.deliverability,
1700
+ activeWorkflows: z.array(z.string()).optional()
1701
+ })
1702
+ ```
1703
+
1704
+ ### `ListIdParamsSchema`
1705
+
1706
+ ```typescript
1707
+ export const ListIdParamsSchema = z.object({
1708
+ listId: UuidSchema
1709
+ })
1710
+ ```
1711
+
1712
+ ### `CreateListRequestSchema`
1713
+
1714
+ ```typescript
1715
+ export const CreateListRequestSchema = z
1716
+ .object({
1717
+ name: z.string().trim().min(1).max(255),
1718
+ description: z.string().trim().nullable().optional(),
1719
+ status: ListStatusSchema.optional(),
1720
+ scrapingConfig: ScrapingConfigSchema.optional(),
1721
+ icp: IcpRubricSchema.optional(),
1722
+ pipelineConfig: PipelineConfigSchema.optional()
1723
+ })
1724
+ .strict()
1725
+ ```
1726
+
1727
+ ### `UpdateListRequestSchema`
1728
+
1729
+ ```typescript
1730
+ export const UpdateListRequestSchema = z
1731
+ .object({
1732
+ name: z.string().trim().min(1).max(255).optional(),
1733
+ description: z.string().trim().nullable().optional(),
1734
+ batchIds: z.array(z.string()).optional()
1735
+ })
1736
+ .strict()
1737
+ .refine((data) => data.name !== undefined || data.description !== undefined || data.batchIds !== undefined, {
1738
+ message: 'At least one field (name, description, or batchIds) must be provided'
1739
+ })
1740
+ ```
1741
+
1742
+ ### `UpdateListConfigRequestSchema`
1743
+
1744
+ ```typescript
1745
+ /**
1746
+ * Partial patch for the three jsonb config columns. UI sends only the edited
1747
+ * subtree; server writes the field as-is (no deep merge — each column is
1748
+ * replaced atomically when present in the patch).
1749
+ */
1750
+ export const UpdateListConfigRequestSchema = z
1751
+ .object({
1752
+ scrapingConfig: ScrapingConfigSchema.partial().optional(),
1753
+ icp: IcpRubricSchema.partial().optional(),
1754
+ pipelineConfig: PipelineConfigSchema.partial().optional()
1755
+ })
1756
+ .strict()
1757
+ .refine((data) => data.scrapingConfig !== undefined || data.icp !== undefined || data.pipelineConfig !== undefined, {
1758
+ message: 'At least one of scrapingConfig, icp, or pipelineConfig must be provided'
1759
+ })
1760
+ ```
1761
+
1762
+ ### `AddCompaniesToListRequestSchema`
1763
+
1764
+ ```typescript
1765
+ export const AddCompaniesToListRequestSchema = z
1766
+ .object({
1767
+ companyIds: z.array(UuidSchema).min(1).max(1000)
1768
+ })
1769
+ .strict()
1770
+ ```
1771
+
1772
+ ### `RemoveCompaniesFromListRequestSchema`
1773
+
1774
+ ```typescript
1775
+ export const RemoveCompaniesFromListRequestSchema = z
1776
+ .object({
1777
+ companyIds: z.array(UuidSchema).min(1).max(1000)
1778
+ })
1779
+ .strict()
1780
+ ```
1781
+
1782
+ ### `AddContactsToListRequestSchema`
1783
+
1784
+ ```typescript
1785
+ export const AddContactsToListRequestSchema = z
1786
+ .object({
1787
+ contactIds: z.array(UuidSchema).min(1).max(1000)
1788
+ })
1789
+ .strict()
1790
+ ```
1791
+
1792
+ ### `RecordListExecutionRequestSchema`
1793
+
1794
+ ```typescript
1795
+ export const RecordListExecutionRequestSchema = z
1796
+ .object({
1797
+ executionId: UuidSchema,
1798
+ configSnapshot: z.record(z.string(), z.unknown()).optional()
1799
+ })
1800
+ .strict()
1801
+ ```
1802
+
1803
+ ### `AcqListResponseSchema`
1804
+
1805
+ ```typescript
1806
+ /**
1807
+ * Single list as returned by /api/acquisition/lists/:id etc.
1808
+ * Camel-cased domain shape matching AcqList in types.ts.
1809
+ */
1810
+ export const AcqListResponseSchema = z.object({
1811
+ id: z.string(),
1812
+ organizationId: z.string(),
1813
+ name: z.string(),
1814
+ description: z.string().nullable(),
1815
+ type: z.string(),
1816
+ batchIds: z.array(z.string()),
1817
+ instantlyCampaignId: z.string().nullable(),
1818
+ /** Lifecycle status (draft | enriching | launched | closing | archived). */
1819
+ status: ListStatusSchema,
1820
+ metadata: z.record(z.string(), z.unknown()),
1821
+ launchedAt: z.string().nullable(),
1822
+ completedAt: z.string().nullable(),
1823
+ createdAt: z.string(),
1824
+ /** Scraping criteria stored as jsonb on the row. */
1825
+ scrapingConfig: ScrapingConfigSchema,
1826
+ /** ICP / qualification rubric stored as jsonb on the row. */
1827
+ icp: IcpRubricSchema,
1828
+ /** Pipeline presentation contract stored as jsonb on the row. */
1829
+ pipelineConfig: PipelineConfigSchema
1830
+ })
1831
+ ```
1832
+
1833
+ ### `AcqListListResponseSchema`
1834
+
1835
+ ```typescript
1836
+ export const AcqListListResponseSchema = z.array(AcqListResponseSchema)
1837
+ ```
1838
+
1839
+ ### `ListTelemetryResponseSchema`
1840
+
1841
+ ```typescript
1842
+ export const ListTelemetryResponseSchema = ListTelemetrySchema
1843
+ ```
1844
+
1845
+ ### `ListTelemetryListResponseSchema`
1846
+
1847
+ ```typescript
1848
+ export const ListTelemetryListResponseSchema = z.array(ListTelemetrySchema)
1849
+ ```
1850
+
1851
+ ### `ListExecutionSummarySchema`
1852
+
1853
+ ```typescript
1854
+ /**
1855
+ * Row from acq_list_executions joined with the execution summary,
1856
+ * shaped for the /lists/:id/executions response.
1857
+ */
1858
+ export const ListExecutionSummarySchema = z.object({
1859
+ executionId: z.string(),
1860
+ resourceId: z.string(),
1861
+ status: z.string(),
1862
+ createdAt: z.string(),
1863
+ completedAt: z.string().nullable(),
1864
+ durationMs: z.number().int().nullable()
1865
+ })
1866
+ ```
1867
+
1868
+ ### `ListExecutionsResponseSchema`
1869
+
1870
+ ```typescript
1871
+ export const ListExecutionsResponseSchema = z.array(ListExecutionSummarySchema)
1872
+ ```
1873
+
1874
+ ### `AcqCompanyStatusSchema`
1875
+
1876
+ ```typescript
1877
+ export const AcqCompanyStatusSchema = z.enum(['active', 'invalid'])
1878
+ ```
1879
+
1880
+ ### `AcqContactStatusSchema`
1881
+
1882
+ ```typescript
1883
+ export const AcqContactStatusSchema = z.enum(['active', 'invalid'])
1884
+ ```
1885
+
1886
+ ### `AcqEmailValidSchema`
1887
+
1888
+ ```typescript
1889
+ export const AcqEmailValidSchema = z.enum(['VALID', 'INVALID', 'RISKY', 'UNKNOWN'])
1890
+ ```
1891
+
1892
+ ### `CompanyIdParamsSchema`
1893
+
1894
+ ```typescript
1895
+ export const CompanyIdParamsSchema = z.object({
1896
+ companyId: UuidSchema
1897
+ })
1898
+ ```
1899
+
1900
+ ### `ContactIdParamsSchema`
1901
+
1902
+ ```typescript
1903
+ export const ContactIdParamsSchema = z.object({
1904
+ contactId: UuidSchema
1905
+ })
1906
+ ```
1907
+
1908
+ ### `ListCompaniesQuerySchema`
1909
+
1910
+ ```typescript
1911
+ export const ListCompaniesQuerySchema = z
1912
+ .object({
1913
+ search: z.string().trim().min(1).max(200).optional(),
1914
+ listId: UuidSchema.optional(),
1915
+ domain: z.string().trim().min(1).max(255).optional(),
1916
+ website: z.string().trim().min(1).max(2048).optional(),
1917
+ segment: z.string().trim().min(1).max(255).optional(),
1918
+ category: z.string().trim().min(1).max(255).optional(),
1919
+ batchId: z.string().trim().min(1).max(255).optional(),
1920
+ status: AcqCompanyStatusSchema.optional(),
1921
+ includeAll: QueryBooleanSchema.optional(),
1922
+ limit: z.coerce.number().int().min(1).max(5000).default(50),
1923
+ offset: z.coerce.number().int().min(0).default(0)
1924
+ })
1925
+ .strict()
1926
+ ```
1927
+
1928
+ ### `ListContactsQuerySchema`
1929
+
1930
+ ```typescript
1931
+ export const ListContactsQuerySchema = z
1932
+ .object({
1933
+ search: z.string().trim().min(1).max(200).optional(),
1934
+ listId: UuidSchema.optional(),
1935
+ openingLineIsNull: QueryBooleanSchema.optional(),
1936
+ batchId: z.string().trim().min(1).max(255).optional(),
1937
+ contactStatus: AcqContactStatusSchema.optional(),
1938
+ limit: z.coerce.number().int().min(1).max(5000).default(5000),
1939
+ offset: z.coerce.number().int().min(0).default(0)
1940
+ })
1941
+ .strict()
1942
+ ```
1943
+
1944
+ ### `CreateCompanyRequestSchema`
1945
+
1946
+ ```typescript
1947
+ export const CreateCompanyRequestSchema = z
1948
+ .object({
1949
+ name: z.string().trim().min(1).max(255),
1950
+ domain: z.string().trim().min(1).max(255).optional(),
1951
+ linkedinUrl: z.string().trim().url().optional(),
1952
+ website: z.string().trim().url().optional(),
1953
+ numEmployees: z.number().int().min(0).optional(),
1954
+ foundedYear: z.number().int().optional(),
1955
+ locationCity: z.string().trim().min(1).max(255).optional(),
1956
+ locationState: z.string().trim().min(1).max(255).optional(),
1957
+ category: z.string().trim().min(1).max(255).optional(),
1958
+ source: z.string().trim().min(1).max(255).optional(),
1959
+ batchId: z.string().trim().min(1).max(255).optional(),
1960
+ verticalResearch: z.string().trim().min(1).max(5000).optional()
1961
+ })
1962
+ .strict()
1963
+ ```
1964
+
1965
+ ### `UpdateCompanyRequestSchema`
1966
+
1967
+ ```typescript
1968
+ export const UpdateCompanyRequestSchema = z
1969
+ .object({
1970
+ name: z.string().trim().min(1).max(255).optional(),
1971
+ domain: z.string().trim().min(1).max(255).optional(),
1972
+ linkedinUrl: z.string().trim().url().optional(),
1973
+ website: z.string().trim().url().optional(),
1974
+ numEmployees: z.number().int().min(0).optional(),
1975
+ foundedYear: z.number().int().optional(),
1976
+ locationCity: z.string().trim().min(1).max(255).optional(),
1977
+ locationState: z.string().trim().min(1).max(255).optional(),
1978
+ category: z.string().trim().min(1).max(255).optional(),
1979
+ segment: z.string().trim().min(1).max(255).optional(),
1980
+ pipelineStatus: z.record(z.string(), z.unknown()).optional(),
1981
+ enrichmentData: z.record(z.string(), z.unknown()).optional(),
1982
+ source: z.string().trim().min(1).max(255).optional(),
1983
+ batchId: z.string().trim().min(1).max(255).optional(),
1984
+ status: AcqCompanyStatusSchema.optional(),
1985
+ verticalResearch: z.string().trim().min(1).max(5000).nullable().optional()
1986
+ })
1987
+ .strict()
1988
+ .refine(
1989
+ (data) =>
1990
+ data.name !== undefined ||
1991
+ data.domain !== undefined ||
1992
+ data.linkedinUrl !== undefined ||
1993
+ data.website !== undefined ||
1994
+ data.numEmployees !== undefined ||
1995
+ data.foundedYear !== undefined ||
1996
+ data.locationCity !== undefined ||
1997
+ data.locationState !== undefined ||
1998
+ data.category !== undefined ||
1999
+ data.segment !== undefined ||
2000
+ data.pipelineStatus !== undefined ||
2001
+ data.enrichmentData !== undefined ||
2002
+ data.source !== undefined ||
2003
+ data.batchId !== undefined ||
2004
+ data.status !== undefined ||
2005
+ data.verticalResearch !== undefined,
2006
+ {
2007
+ message: 'At least one field must be provided'
2008
+ }
2009
+ )
2010
+ ```
2011
+
2012
+ ### `CreateContactRequestSchema`
2013
+
2014
+ ```typescript
2015
+ export const CreateContactRequestSchema = z
2016
+ .object({
2017
+ email: z.string().trim().email(),
2018
+ companyId: UuidSchema.optional(),
2019
+ firstName: z.string().trim().min(1).max(255).optional(),
2020
+ lastName: z.string().trim().min(1).max(255).optional(),
2021
+ linkedinUrl: z.string().trim().url().optional(),
2022
+ title: z.string().trim().min(1).max(255).optional(),
2023
+ source: z.string().trim().min(1).max(255).optional(),
2024
+ sourceId: z.string().trim().min(1).max(255).optional(),
2025
+ batchId: z.string().trim().min(1).max(255).optional()
2026
+ })
2027
+ .strict()
2028
+ ```
2029
+
2030
+ ### `UpdateContactRequestSchema`
2031
+
2032
+ ```typescript
2033
+ export const UpdateContactRequestSchema = z
2034
+ .object({
2035
+ companyId: UuidSchema.optional(),
2036
+ emailValid: AcqEmailValidSchema.optional(),
2037
+ firstName: z.string().trim().min(1).max(255).optional(),
2038
+ lastName: z.string().trim().min(1).max(255).optional(),
2039
+ linkedinUrl: z.string().trim().url().optional(),
2040
+ title: z.string().trim().min(1).max(255).optional(),
2041
+ headline: z.string().trim().min(1).max(5000).optional(),
2042
+ filterReason: z.string().trim().min(1).max(5000).optional(),
2043
+ openingLine: z.string().trim().min(1).max(5000).optional(),
2044
+ pipelineStatus: z.record(z.string(), z.unknown()).optional(),
2045
+ enrichmentData: z.record(z.string(), z.unknown()).optional(),
2046
+ status: AcqContactStatusSchema.optional()
2047
+ })
2048
+ .strict()
2049
+ .refine(
2050
+ (data) =>
2051
+ data.companyId !== undefined ||
2052
+ data.emailValid !== undefined ||
2053
+ data.firstName !== undefined ||
2054
+ data.lastName !== undefined ||
2055
+ data.linkedinUrl !== undefined ||
2056
+ data.title !== undefined ||
2057
+ data.headline !== undefined ||
2058
+ data.filterReason !== undefined ||
2059
+ data.openingLine !== undefined ||
2060
+ data.pipelineStatus !== undefined ||
2061
+ data.enrichmentData !== undefined ||
2062
+ data.status !== undefined,
2063
+ {
2064
+ message: 'At least one field must be provided'
2065
+ }
2066
+ )
2067
+ ```
2068
+
2069
+ ### `AcqCompanyResponseSchema`
2070
+
2071
+ ```typescript
2072
+ export const AcqCompanyResponseSchema = z.object({
2073
+ id: z.string(),
2074
+ organizationId: z.string(),
2075
+ name: z.string(),
2076
+ domain: z.string().nullable(),
2077
+ linkedinUrl: z.string().nullable(),
2078
+ website: z.string().nullable(),
2079
+ numEmployees: z.number().nullable(),
2080
+ foundedYear: z.number().nullable(),
2081
+ locationCity: z.string().nullable(),
2082
+ locationState: z.string().nullable(),
2083
+ category: z.string().nullable(),
2084
+ categoryPain: z.string().nullable(),
2085
+ segment: z.string().nullable(),
2086
+ pipelineStatus: z.record(z.string(), z.unknown()).nullable(),
2087
+ enrichmentData: z.record(z.string(), z.unknown()).nullable(),
2088
+ source: z.string().nullable(),
2089
+ batchId: z.string().nullable(),
2090
+ status: AcqCompanyStatusSchema,
2091
+ contactCount: z.number().int().min(0),
2092
+ verticalResearch: z.string().nullable(),
2093
+ createdAt: z.string(),
2094
+ updatedAt: z.string()
2095
+ })
2096
+ ```
2097
+
2098
+ ### `AcqCompanyListResponseSchema`
2099
+
2100
+ ```typescript
2101
+ export const AcqCompanyListResponseSchema = z.object({
2102
+ data: z.array(AcqCompanyResponseSchema),
2103
+ total: z.number().int(),
2104
+ limit: z.number().int(),
2105
+ offset: z.number().int()
2106
+ })
2107
+ ```
2108
+
2109
+ ### `AcqCompanyFacetsResponseSchema`
2110
+
2111
+ ```typescript
2112
+ export const AcqCompanyFacetsResponseSchema = z.object({
2113
+ segments: z.array(z.string()),
2114
+ categories: z.array(z.string()),
2115
+ statuses: z.array(AcqCompanyStatusSchema)
2116
+ })
2117
+ ```
2118
+
2119
+ ### `AcqContactCompanySummarySchema`
2120
+
2121
+ ```typescript
2122
+ export const AcqContactCompanySummarySchema = z.object({
2123
+ id: z.string(),
2124
+ name: z.string(),
2125
+ domain: z.string().nullable(),
2126
+ website: z.string().nullable(),
2127
+ linkedinUrl: z.string().nullable(),
2128
+ segment: z.string().nullable(),
2129
+ category: z.string().nullable(),
2130
+ status: AcqCompanyStatusSchema
2131
+ })
2132
+ ```
2133
+
2134
+ ### `AcqContactResponseSchema`
2135
+
2136
+ ```typescript
2137
+ export const AcqContactResponseSchema = z.object({
2138
+ id: z.string(),
2139
+ organizationId: z.string(),
2140
+ companyId: z.string().nullable(),
2141
+ email: z.string(),
2142
+ emailValid: AcqEmailValidSchema.nullable(),
2143
+ firstName: z.string().nullable(),
2144
+ lastName: z.string().nullable(),
2145
+ linkedinUrl: z.string().nullable(),
2146
+ title: z.string().nullable(),
2147
+ headline: z.string().nullable(),
2148
+ filterReason: z.string().nullable(),
2149
+ openingLine: z.string().nullable(),
2150
+ source: z.string().nullable(),
2151
+ sourceId: z.string().nullable(),
2152
+ pipelineStatus: z.record(z.string(), z.unknown()).nullable(),
2153
+ enrichmentData: z.record(z.string(), z.unknown()).nullable(),
2154
+ attioPersonId: z.string().nullable(),
2155
+ batchId: z.string().nullable(),
2156
+ status: AcqContactStatusSchema,
2157
+ company: AcqContactCompanySummarySchema.nullable().optional(),
2158
+ createdAt: z.string(),
2159
+ updatedAt: z.string()
2160
+ })
2161
+ ```
2162
+
2163
+ ### `AcqContactListResponseSchema`
2164
+
2165
+ ```typescript
2166
+ export const AcqContactListResponseSchema = z.object({
2167
+ data: z.array(AcqContactResponseSchema),
2168
+ total: z.number().int(),
2169
+ limit: z.number().int(),
2170
+ offset: z.number().int()
2171
+ })
2172
+ ```
2173
+
2174
+ ### `AcqArtifactOwnerKindSchema`
2175
+
2176
+ ```typescript
2177
+ export const AcqArtifactOwnerKindSchema = z.enum(['company', 'contact', 'deal', 'list', 'list_member'])
2178
+ ```
2179
+
2180
+ ### `ListArtifactsQuerySchema`
2181
+
2182
+ ```typescript
2183
+ export const ListArtifactsQuerySchema = z
2184
+ .object({
2185
+ ownerKind: AcqArtifactOwnerKindSchema,
2186
+ ownerId: UuidSchema
2187
+ })
2188
+ .strict()
2189
+ ```
2190
+
2191
+ ### `CreateArtifactRequestSchema`
2192
+
2193
+ ```typescript
2194
+ export const CreateArtifactRequestSchema = z
2195
+ .object({
2196
+ ownerKind: AcqArtifactOwnerKindSchema,
2197
+ ownerId: UuidSchema,
2198
+ kind: z.string().trim().min(1).max(255),
2199
+ content: z.record(z.string(), z.unknown()),
2200
+ sourceExecutionId: UuidSchema.optional()
2201
+ })
2202
+ .strict()
2203
+ ```
2204
+
2205
+ ### `AcqArtifactResponseSchema`
2206
+
2207
+ ```typescript
2208
+ export const AcqArtifactResponseSchema = z.object({
2209
+ id: z.string(),
2210
+ organizationId: z.string(),
2211
+ ownerKind: z.string(),
2212
+ ownerId: z.string(),
2213
+ kind: z.string(),
2214
+ content: z.record(z.string(), z.unknown()),
2215
+ sourceExecutionId: z.string().nullable(),
2216
+ createdBy: z.string().nullable(),
2217
+ createdAt: z.string(),
2218
+ version: z.number().int()
2219
+ })
2220
+ ```
2221
+
2222
+ ### `AcqArtifactListResponseSchema`
2223
+
2224
+ ```typescript
2225
+ export const AcqArtifactListResponseSchema = z.object({
2226
+ artifacts: z.array(AcqArtifactResponseSchema)
2227
+ })
2228
+ ```
2229
+
2230
+ ### `ListMembersQuerySchema`
2231
+
2232
+ ```typescript
2233
+ export const ListMembersQuerySchema = z
2234
+ .object({
2235
+ limit: z.coerce.number().int().min(1).max(500).default(50),
2236
+ offset: z.coerce.number().int().min(0).default(0)
2237
+ })
2238
+ .strict()
2239
+ ```
2240
+
2241
+ ### `MemberIdParamsSchema`
2242
+
2243
+ ```typescript
2244
+ export const MemberIdParamsSchema = z.object({
2245
+ memberId: UuidSchema
2246
+ })
2247
+ ```
2248
+
2249
+ ### `AcqListMemberContactSummarySchema`
2250
+
2251
+ ```typescript
2252
+ export const AcqListMemberContactSummarySchema = z.object({
2253
+ id: z.string(),
2254
+ email: z.string(),
2255
+ firstName: z.string().nullable(),
2256
+ lastName: z.string().nullable(),
2257
+ title: z.string().nullable(),
2258
+ linkedinUrl: z.string().nullable(),
2259
+ companyId: z.string().nullable()
2260
+ })
2261
+ ```
2262
+
2263
+ ### `AcqListMemberResponseSchema`
2264
+
2265
+ ```typescript
2266
+ export const AcqListMemberResponseSchema = z.object({
2267
+ id: z.string(),
2268
+ listId: z.string(),
2269
+ contactId: z.string(),
2270
+ pipelineKey: z.string(),
2271
+ stageKey: z.string(),
2272
+ stateKey: z.string(),
2273
+ activityLog: z.unknown(),
2274
+ addedAt: z.string(),
2275
+ addedBy: z.string().nullable(),
2276
+ sourceExecutionId: z.string().nullable(),
2277
+ contact: AcqListMemberContactSummarySchema.nullable()
2278
+ })
2279
+ ```
2280
+
2281
+ ### `AcqListMembersResponseSchema`
2282
+
2283
+ ```typescript
2284
+ export const AcqListMembersResponseSchema = z.object({
2285
+ members: z.array(AcqListMemberResponseSchema)
2286
+ })
2287
+ ```
2288
+
2289
+ ### `ListCompanyIdParamsSchema`
2290
+
2291
+ ```typescript
2292
+ export const ListCompanyIdParamsSchema = z.object({
2293
+ listCompanyId: UuidSchema
2294
+ })
2295
+ ```
2296
+
2297
+ ### `AcqListCompanyResponseSchema`
2298
+
2299
+ ```typescript
2300
+ export const AcqListCompanyResponseSchema = z.object({
2301
+ id: z.string(),
2302
+ listId: z.string(),
2303
+ companyId: z.string(),
2304
+ pipelineKey: z.string(),
2305
+ stageKey: z.string(),
2306
+ stateKey: z.string(),
2307
+ activityLog: z.unknown(),
2308
+ addedAt: z.string(),
2309
+ addedBy: z.string().nullable(),
2310
+ sourceExecutionId: z.string().nullable()
2311
+ })
2312
+ ```
2313
+
2314
+ ### `AcqCompanySchemas`
2315
+
2316
+ ```typescript
2317
+ export const AcqCompanySchemas = {
2318
+ CompanyIdParams: CompanyIdParamsSchema,
2319
+ ListCompaniesQuery: ListCompaniesQuerySchema,
2320
+ CreateCompanyRequest: CreateCompanyRequestSchema,
2321
+ UpdateCompanyRequest: UpdateCompanyRequestSchema,
2322
+ AcqCompanyResponse: AcqCompanyResponseSchema,
2323
+ AcqCompanyListResponse: AcqCompanyListResponseSchema,
2324
+ AcqCompanyFacetsResponse: AcqCompanyFacetsResponseSchema
2325
+ }
2326
+ ```
2327
+
2328
+ ### `AcqContactSchemas`
2329
+
2330
+ ```typescript
2331
+ export const AcqContactSchemas = {
2332
+ ContactIdParams: ContactIdParamsSchema,
2333
+ ListContactsQuery: ListContactsQuerySchema,
2334
+ CreateContactRequest: CreateContactRequestSchema,
2335
+ UpdateContactRequest: UpdateContactRequestSchema,
2336
+ AcqContactResponse: AcqContactResponseSchema,
2337
+ AcqContactListResponse: AcqContactListResponseSchema
2338
+ }
2339
+ ```
2340
+
2341
+ ### `AcqListSchemas`
2342
+
2343
+ ```typescript
2344
+ export const AcqListSchemas = {
2345
+ // Params
2346
+ ListIdParams: ListIdParamsSchema,
2347
+
2348
+ // Primitives (for UI / tests)
2349
+ ListStatus: ListStatusSchema,
2350
+ ScrapingConfig: ScrapingConfigSchema,
2351
+ IcpRubric: IcpRubricSchema,
2352
+ PipelineConfig: PipelineConfigSchema,
2353
+ PipelineStage: PipelineStageSchema,
2354
+ ListStageCounts: ListStageCountsSchema,
2355
+ ListTelemetry: ListTelemetrySchema,
2356
+
2357
+ // Requests
2358
+ CreateListRequest: CreateListRequestSchema,
2359
+ UpdateListRequest: UpdateListRequestSchema,
2360
+ UpdateListStatusRequest: UpdateListStatusRequestSchema,
2361
+ UpdateListConfigRequest: UpdateListConfigRequestSchema,
2362
+ AddCompaniesToListRequest: AddCompaniesToListRequestSchema,
2363
+ RemoveCompaniesFromListRequest: RemoveCompaniesFromListRequestSchema,
2364
+ AddContactsToListRequest: AddContactsToListRequestSchema,
2365
+ RecordListExecutionRequest: RecordListExecutionRequestSchema,
2366
+
2367
+ // Responses
2368
+ AcqListResponse: AcqListResponseSchema,
2369
+ AcqListListResponse: AcqListListResponseSchema,
2370
+ ListTelemetryResponse: ListTelemetryResponseSchema,
2371
+ ListTelemetryListResponse: ListTelemetryListResponseSchema,
2372
+ ListExecutionsResponse: ListExecutionsResponseSchema,
2373
+ ListProgressResponse: ListProgressResponseSchema
2374
+ }
2375
+ ```
2376
+
2377
+ ### `AcqSubstrateSchemas`
2378
+
2379
+ ```typescript
2380
+ export const AcqSubstrateSchemas = {
2381
+ // Artifacts
2382
+ ListArtifactsQuery: ListArtifactsQuerySchema,
2383
+ CreateArtifactRequest: CreateArtifactRequestSchema,
2384
+ AcqArtifactResponse: AcqArtifactResponseSchema,
2385
+ AcqArtifactListResponse: AcqArtifactListResponseSchema,
2386
+
2387
+ // List members
2388
+ ListMembersQuery: ListMembersQuerySchema,
2389
+ MemberIdParams: MemberIdParamsSchema,
2390
+ AcqListMemberResponse: AcqListMemberResponseSchema,
2391
+ AcqListMembersResponse: AcqListMembersResponseSchema,
2392
+
2393
+ // List companies
2394
+ ListCompanyIdParams: ListCompanyIdParamsSchema,
2395
+ AcqListCompanyResponse: AcqListCompanyResponseSchema,
2396
+
2397
+ // Transition (shared with deals — TransitionItemRequestSchema)
2398
+ TransitionItemRequest: TransitionItemRequestSchema
2399
+ }
2400
+ ```
2401
+
2402
+ ### `Stateful`
2403
+
2404
+ ```typescript
2405
+ /**
2406
+ * Stateful trait — the (pipeline_key, stage_key, state_key, activity_log) quartet
2407
+ * applied to acq_deals (CRM HITL, shipped 2026-04-27) and being generalized to
2408
+ * acq_lists / acq_list_members / acq_list_companies via Track B.
2409
+ */
2410
+ export interface Stateful {
2411
+ pipeline_key: string
2412
+ stage_key: string
2413
+ state_key: string
2414
+ activity_log: ActivityEvent[]
2415
+ }
2416
+ ```
2417
+
2418
+ ### `TransitionItem`
2419
+
2420
+ ```typescript
2421
+ /** Generic transition shape — concrete per-entity transitionItem implementations satisfy this. */
2422
+ export type TransitionItem<T extends Stateful, TEvent extends ActivityEvent> = (
2423
+ item: T,
2424
+ transition: { stage_key?: string; state_key?: string; event: TEvent }
2425
+ ) => T
2426
+ ```
2427
+
2428
+ ### `DeriveActions`
2429
+
2430
+ ```typescript
2431
+ /** Generic action-derivation shape — concrete per-entity deriveActions implementations satisfy this. */
2432
+ export type DeriveActions<T extends Stateful, TAction> = (item: T) => TAction[]
2433
+ ```
2434
+
2435
+ ### `StatefulSchema`
2436
+
2437
+ ```typescript
2438
+ export const StatefulSchema = z.object({
2439
+ pipeline_key: z.string(),
2440
+ stage_key: z.string(),
2441
+ state_key: z.string(),
2442
+ activity_log: z.array(ActivityEventSchema)
2443
+ })
2444
+ ```
2445
+
2446
+ ### `StatefulStateDefinition`
2447
+
2448
+ ```typescript
2449
+ /** One state within a stage — minimal shape: key + display label. */
2450
+ export interface StatefulStateDefinition {
2451
+ /** Matches state_key values written by workflow steps. */
2452
+ stateKey: string
2453
+ label: string
2454
+ }
2455
+ ```
2456
+
2457
+ ### `StatefulStageDefinition`
2458
+
2459
+ ```typescript
2460
+ /** One stage within a pipeline — has a stage_key and an ordered list of valid states. */
2461
+ export interface StatefulStageDefinition {
2462
+ /** Matches stage_key values written by workflow steps. */
2463
+ stageKey: string
2464
+ label: string
2465
+ states: StatefulStateDefinition[]
2466
+ }
2467
+ ```
2468
+
2469
+ ### `StatefulPipelineDefinition`
2470
+
2471
+ ```typescript
2472
+ /**
2473
+ * Pipeline definition for a single entity participating in the Stateful trait.
2474
+ * Parallel to acq_deals' pipeline_key concept but structured for lead-gen entities.
2475
+ */
2476
+ export interface StatefulPipelineDefinition {
2477
+ /** Matches pipeline_key values in the database (e.g. 'lead-gen'). */
2478
+ pipelineKey: string
2479
+ label: string
2480
+ /** Entity this pipeline applies to (e.g. 'acq.list', 'acq.list-member', 'acq.list-company'). */
2481
+ entityKey: string
2482
+ stages: StatefulStageDefinition[]
2483
+ }
2484
+ ```
2485
+
2486
+ ### `ACQ_LIST_MEMBERS_LEAD_GEN_PIPELINE`
2487
+
2488
+ ```typescript
2489
+ /**
2490
+ * Lead-gen pipeline definition for acq_list_members (contacts).
2491
+ * Three stages matching the post-restructure sales subdomain tree.
2492
+ *
2493
+ * Note: members visit outreach and prospecting states depending on which
2494
+ * workflow last processed them. stage_key is set per-transition by the workflow.
2495
+ */
2496
+ export const ACQ_LIST_MEMBERS_LEAD_GEN_PIPELINE: StatefulPipelineDefinition = {
2497
+ pipelineKey: 'lead-gen',
2498
+ label: 'Lead Generation',
2499
+ entityKey: 'acq.list-member',
2500
+ stages: [
2501
+ {
2502
+ stageKey: 'outreach',
2503
+ label: 'Outreach',
2504
+ states: [
2505
+ PENDING_STATE,
2506
+ { stateKey: 'personalized', label: 'Personalized' },
2507
+ { stateKey: 'uploaded', label: 'Uploaded' },
2508
+ { stateKey: 'interested', label: 'Interested' }
2509
+ ]
2510
+ },
2511
+ {
2512
+ stageKey: 'prospecting',
2513
+ label: 'Prospecting',
2514
+ states: [
2515
+ PENDING_STATE,
2516
+ { stateKey: 'discovered', label: 'Discovered' },
2517
+ { stateKey: 'verified', label: 'Verified' }
2518
+ ]
2519
+ },
2520
+ {
2521
+ stageKey: 'qualification',
2522
+ label: 'Qualification',
2523
+ states: [PENDING_STATE]
2524
+ }
2525
+ ]
2526
+ }
2527
+ ```
2528
+
2529
+ ### `ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE`
2530
+
2531
+ ```typescript
2532
+ /**
2533
+ * Lead-gen pipeline definition for acq_list_companies.
2534
+ * Three stages matching the post-restructure sales subdomain tree.
2535
+ *
2536
+ * Note: companies visit prospecting and qualification states depending on which
2537
+ * workflow last processed them. stage_key is set per-transition by the workflow.
2538
+ */
2539
+ export const ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE: StatefulPipelineDefinition = {
2540
+ pipelineKey: 'lead-gen',
2541
+ label: 'Lead Generation',
2542
+ entityKey: 'acq.list-company',
2543
+ stages: [
2544
+ {
2545
+ stageKey: 'outreach',
2546
+ label: 'Outreach',
2547
+ states: [PENDING_STATE]
2548
+ },
2549
+ {
2550
+ stageKey: 'prospecting',
2551
+ label: 'Prospecting',
2552
+ states: [
2553
+ PENDING_STATE,
2554
+ { stateKey: 'populated', label: 'Populated' },
2555
+ { stateKey: 'extracted', label: 'Extracted' }
2556
+ ]
2557
+ },
2558
+ {
2559
+ stageKey: 'qualification',
2560
+ label: 'Qualification',
2561
+ states: [PENDING_STATE, { stateKey: 'qualified', label: 'Qualified' }]
2562
+ }
2563
+ ]
2564
+ }
2565
+ ```
2566
+
2567
+ ### `LEAD_GEN_PIPELINE_DEFINITIONS`
2568
+
2569
+ ```typescript
2570
+ /**
2571
+ * All lead-gen pipeline definitions indexed by entity key.
2572
+ * Use findPipeline() to locate a definition by pipeline_key within any of these arrays.
2573
+ */
2574
+ export const LEAD_GEN_PIPELINE_DEFINITIONS: Record<string, StatefulPipelineDefinition[]> = {
2575
+ 'acq.list-member': [ACQ_LIST_MEMBERS_LEAD_GEN_PIPELINE],
2576
+ 'acq.list-company': [ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE]
2577
+ }
2578
+ ```
2579
+
2580
+ ### `PaginationParams`
2581
+
2582
+ ```typescript
2583
+ export interface PaginationParams {
2584
+ limit: number
2585
+ offset: number
2586
+ }
2587
+ ```
2588
+
2589
+ ### `PaginatedResult`
2590
+
2591
+ ```typescript
2592
+ export interface PaginatedResult<T> {
2593
+ data: T[]
2594
+ total: number
2595
+ limit: number
2596
+ offset: number
2597
+ }
2598
+ ```
2599
+
2600
+ ### `CreateListParams`
2601
+
2602
+ ```typescript
2603
+ export interface CreateListParams {
2604
+ organizationId: string
2605
+ name: string
2606
+ description?: string
2607
+ type?: string
2608
+ batchIds?: string[]
2609
+ instantlyCampaignId?: string
2610
+ status?: ListStatus
2611
+ metadata?: Record<string, unknown>
2612
+ scrapingConfig?: ScrapingConfig
2613
+ icp?: IcpRubric
2614
+ pipelineConfig?: PipelineConfig
2615
+ }
2616
+ ```
2617
+
2618
+ ### `UpdateListParams`
2619
+
2620
+ ```typescript
2621
+ export interface UpdateListParams {
2622
+ name?: string
2623
+ description?: string
2624
+ batchIds?: string[]
2625
+ }
2626
+ ```
2627
+
2628
+ ### `CreateCompanyParams`
2629
+
2630
+ ```typescript
2631
+ export interface CreateCompanyParams {
2632
+ organizationId: string
2633
+ name: string
2634
+ domain?: string
2635
+ linkedinUrl?: string
2636
+ website?: string
2637
+ numEmployees?: number
2638
+ foundedYear?: number
2639
+ locationCity?: string
2640
+ locationState?: string
2641
+ category?: string
2642
+ source?: string
2643
+ batchId?: string
2644
+ verticalResearch?: string
2645
+ }
2646
+ ```
2647
+
2648
+ ### `UpdateCompanyParams`
2649
+
2650
+ ```typescript
2651
+ export interface UpdateCompanyParams {
2652
+ name?: string
2653
+ domain?: string
2654
+ linkedinUrl?: string
2655
+ website?: string
2656
+ numEmployees?: number
2657
+ foundedYear?: number
2658
+ locationCity?: string
2659
+ locationState?: string
2660
+ category?: string
2661
+ segment?: string
2662
+ pipelineStatus?: Record<string, unknown>
2663
+ enrichmentData?: Record<string, unknown>
2664
+ source?: string
2665
+ batchId?: string
2666
+ status?: 'active' | 'invalid'
2667
+ verticalResearch?: string | null
2668
+ /** Track A: flat qualification score column (null until a scoring rubric is defined) */
2669
+ qualificationScore?: number | null
2670
+ /** Track A: flat qualification signals jsonb — mirrors the former pipeline_status.qualification shape */
2671
+ qualificationSignals?: Record<string, unknown> | null
2672
+ /** Track A: key identifying the rubric used for qualification */
2673
+ qualificationRubricKey?: string | null
2674
+ }
2675
+ ```
2676
+
2677
+ ### `UpsertCompanyParams`
2678
+
2679
+ ```typescript
2680
+ export type UpsertCompanyParams = CreateCompanyParams
2681
+ ```
2682
+
2683
+ ### `CompanyFilters`
2684
+
2685
+ ```typescript
2686
+ export interface CompanyFilters {
2687
+ listId?: string // Filter to companies in a specific list (via acq_list_companies)
2688
+ search?: string
2689
+ domain?: string
2690
+ website?: string
2691
+ segment?: string
2692
+ category?: string
2693
+ pipelineStatus?: Record<string, unknown>
2694
+ /** Exclude companies whose pipeline_status contains this value (PostgREST NOT contains) */
2695
+ pipelineStatusNot?: Record<string, unknown>
2696
+ batchId?: string
2697
+ status?: 'active' | 'invalid'
2698
+ includeAll?: boolean
2699
+ excludeColumns?: Array<'enrichmentData' | 'pipelineStatus'>
2700
+ }
2701
+ ```
2702
+
2703
+ ### `CreateContactParams`
2704
+
2705
+ ```typescript
2706
+ export interface CreateContactParams {
2707
+ organizationId: string
2708
+ email: string
2709
+ companyId?: string
2710
+ firstName?: string
2711
+ lastName?: string
2712
+ linkedinUrl?: string
2713
+ title?: string
2714
+ source?: string
2715
+ sourceId?: string
2716
+ batchId?: string
2717
+ }
2718
+ ```
2719
+
2720
+ ### `UpdateContactParams`
2721
+
2722
+ ```typescript
2723
+ export interface UpdateContactParams {
2724
+ companyId?: string
2725
+ emailValid?: 'VALID' | 'INVALID' | 'RISKY' | 'UNKNOWN'
2726
+ firstName?: string
2727
+ lastName?: string
2728
+ linkedinUrl?: string
2729
+ title?: string
2730
+ headline?: string
2731
+ filterReason?: string
2732
+ openingLine?: string
2733
+ pipelineStatus?: Record<string, unknown>
2734
+ enrichmentData?: Record<string, unknown>
2735
+ status?: 'active' | 'invalid'
2736
+ }
2737
+ ```
2738
+
2739
+ ### `UpsertContactParams`
2740
+
2741
+ ```typescript
2742
+ export type UpsertContactParams = CreateContactParams
2743
+ ```
2744
+
2745
+ ### `ContactFilters`
2746
+
2747
+ ```typescript
2748
+ export interface ContactFilters {
2749
+ listId?: string // Filter to contacts in a specific list (via acq_list_members)
2750
+ search?: string
2751
+ openingLineIsNull?: boolean // Filter to contacts without personalization
2752
+ pipelineStatus?: Record<string, unknown>
2753
+ batchId?: string
2754
+ contactStatus?: 'active' | 'invalid' // Filter by contact status (soft-delete flag)
2755
+ }
2756
+ ```
2757
+
2758
+ ### `UpsertSocialPostParams`
2759
+
2760
+ ```typescript
2761
+ export interface UpsertSocialPostParams {
2762
+ organizationId: string
2763
+ platform: string
2764
+ platformPostId: string
2765
+ authorName: string
2766
+ authorUrl?: string | null
2767
+ postTitle: string
2768
+ postText: string
2769
+ postUrl: string
2770
+ engagementCount?: number
2771
+ commentsCount?: number
2772
+ postedAt: string
2773
+ metadata?: Record<string, unknown>
2774
+ relevanceScore?: number
2775
+ matchedKeywords?: string[]
2776
+ matchedQuery?: string | null
2777
+ initialDraft?: string | null
2778
+ finalResponse?: string | null
2779
+ sourceCategory?: string | null
2780
+ }
2781
+ ```
2782
+
2783
+ ### `UpsertSocialPostsParams`
2784
+
2785
+ ```typescript
2786
+ export interface UpsertSocialPostsParams {
2787
+ organizationId: string
2788
+ posts: Omit<UpsertSocialPostParams, 'organizationId'>[]
2789
+ }
2790
+ ```
2791
+
2792
+ ### `UpsertSocialPostsResult`
2793
+
2794
+ ```typescript
2795
+ export interface UpsertSocialPostsResult {
2796
+ inserted: number
2797
+ duplicatesSkipped: number
2798
+ }
2799
+ ```
2800
+
2801
+ ### `AddContactsToListParams`
2802
+
2803
+ ```typescript
2804
+ export interface AddContactsToListParams {
2805
+ organizationId: string
2806
+ listId: string
2807
+ contactIds: string[]
2808
+ }
2809
+ ```
2810
+
2811
+ ### `AddContactsToListResult`
2812
+
2813
+ ```typescript
2814
+ export interface AddContactsToListResult {
2815
+ added: number
2816
+ alreadyExisted: number
2817
+ }
2818
+ ```
2819
+
2820
+ ### `UpdateListConfigParams`
2821
+
2822
+ ```typescript
2823
+ export interface UpdateListConfigParams {
2824
+ organizationId: string
2825
+ listId: string
2826
+ scrapingConfig?: ScrapingConfig
2827
+ icp?: IcpRubric
2828
+ pipelineConfig?: PipelineConfig
2829
+ }
2830
+ ```
2831
+
2832
+ ### `UpdateCompanyStageParams`
2833
+
2834
+ ```typescript
2835
+ export interface UpdateCompanyStageParams {
2836
+ organizationId: string
2837
+ listId: string
2838
+ companyId: string
2839
+ stage: string
2840
+ executionId?: string
2841
+ }
2842
+ ```
2843
+
2844
+ ### `UpdateContactStageParams`
2845
+
2846
+ ```typescript
2847
+ export interface UpdateContactStageParams {
2848
+ organizationId: string
2849
+ listId: string
2850
+ contactId: string
2851
+ stage: string
2852
+ executionId?: string
2853
+ }
2854
+ ```
2855
+
2856
+ ### `AddCompaniesToListParams`
2857
+
2858
+ ```typescript
2859
+ export interface AddCompaniesToListParams {
2860
+ organizationId: string
2861
+ listId: string
2862
+ companyIds: string[]
2863
+ }
2864
+ ```
2865
+
2866
+ ### `AddCompaniesToListResult`
2867
+
2868
+ ```typescript
2869
+ export interface AddCompaniesToListResult {
2870
+ added: number
2871
+ alreadyExisted: number
2872
+ }
2873
+ ```
2874
+
2875
+ ### `RemoveCompaniesFromListParams`
2876
+
2877
+ ```typescript
2878
+ export interface RemoveCompaniesFromListParams {
2879
+ organizationId: string
2880
+ listId: string
2881
+ companyIds: string[]
2882
+ }
2883
+ ```
2884
+
2885
+ ### `RemoveCompaniesFromListResult`
2886
+
2887
+ ```typescript
2888
+ export interface RemoveCompaniesFromListResult {
2889
+ removed: number
2890
+ }
2891
+ ```
2892
+
2893
+ ### `RecordListExecutionParams`
2894
+
2895
+ ```typescript
2896
+ export interface RecordListExecutionParams {
2897
+ organizationId: string
2898
+ listId: string
2899
+ executionId: string
2900
+ configSnapshot?: Record<string, unknown>
2901
+ }
2902
+ ```
2903
+
2904
+ ### `ListExecutionSummary`
2905
+
2906
+ ```typescript
2907
+ export interface ListExecutionSummary {
2908
+ executionId: string
2909
+ resourceId: string
2910
+ status: string
2911
+ createdAt: string
2912
+ completedAt: string | null
2913
+ durationMs: number | null
2914
+ }
2915
+ ```
2916
+
2917
+ ### `BulkImportParams`
2918
+
2919
+ ```typescript
2920
+ export interface BulkImportParams {
2921
+ organizationId: string
2922
+ contacts: CreateContactParams[]
2923
+ listId?: string
2924
+ }
2925
+ ```
2926
+
2927
+ ### `BulkImportResult`
2928
+
2929
+ ```typescript
2930
+ export interface BulkImportResult {
2931
+ created: number
2932
+ updated: number
2933
+ errors: Array<{ email: string; error: string }>
2934
+ }
2935
+ ```
2936
+
2937
+ ### `BulkImportCompanyEntry`
2938
+
2939
+ ```typescript
2940
+ export interface BulkImportCompanyEntry {
2941
+ name: string
2942
+ domain: string
2943
+ website?: string
2944
+ locationCity?: string
2945
+ locationState?: string
2946
+ category?: string
2947
+ source?: string
2948
+ enrichmentData?: Record<string, unknown>
2949
+ pipelineStatus?: Record<string, unknown>
2950
+ }
2951
+ ```
2952
+
2953
+ ### `BulkImportCompaniesParams`
2954
+
2955
+ ```typescript
2956
+ export interface BulkImportCompaniesParams {
2957
+ organizationId: string
2958
+ batchId: string
2959
+ companies: BulkImportCompanyEntry[]
2960
+ }
2961
+ ```
2962
+
2963
+ ### `BulkImportCompaniesResult`
2964
+
2965
+ ```typescript
2966
+ export interface BulkImportCompaniesResult {
2967
+ created: number
2968
+ skipped: number
2969
+ errors: Array<{ companyName: string; error: string }>
2970
+ }
2971
+ ```
2972
+
2973
+ ### `LeadToolMap`
2974
+
2975
+ ```typescript
2976
+ export type LeadToolMap = {
2977
+ // List operations
2978
+ listLists: { params: Record<string, never>; result: AcqList[] }
2979
+ createList: { params: Omit<CreateListParams, 'organizationId'>; result: AcqList }
2980
+ updateList: { params: { id: string } & UpdateListParams; result: AcqList }
2981
+ deleteList: { params: { id: string }; result: void }
2982
+ addContactsToList: { params: Omit<AddContactsToListParams, 'organizationId'>; result: AddContactsToListResult }
2983
+ updateCompanyStage: {
2984
+ params: Omit<UpdateCompanyStageParams, 'organizationId'>
2985
+ result: void
2986
+ }
2987
+ updateContactStage: {
2988
+ params: Omit<UpdateContactStageParams, 'organizationId'>
2989
+ result: void
2990
+ }
2991
+ // Company operations
2992
+ createCompany: { params: Omit<CreateCompanyParams, 'organizationId'>; result: AcqCompany }
2993
+ upsertCompany: { params: Omit<UpsertCompanyParams, 'organizationId'>; result: AcqCompany }
2994
+ updateCompany: { params: { id: string } & UpdateCompanyParams; result: AcqCompany }
2995
+ getCompany: { params: { id: string }; result: AcqCompany | null }
2996
+ listCompanies: { params: CompanyFilters; result: AcqCompany[] }
2997
+ deleteCompany: { params: { id: string }; result: void }
2998
+ // Contact operations
2999
+ createContact: { params: Omit<CreateContactParams, 'organizationId'>; result: AcqContact }
3000
+ upsertContact: { params: Omit<UpsertContactParams, 'organizationId'>; result: AcqContact }
3001
+ updateContact: { params: { id: string } & UpdateContactParams; result: AcqContact }
3002
+ getContact: { params: { id: string }; result: AcqContact | null }
3003
+ getContactByEmail: { params: { email: string }; result: AcqContact | null }
3004
+ listContacts: {
3005
+ params: ContactFilters & { limit?: number; offset?: number }
3006
+ result: PaginatedResult<AcqContact>
3007
+ }
3008
+ deleteContact: { params: { id: string }; result: void }
3009
+ bulkImportContacts: { params: Omit<BulkImportParams, 'organizationId'>; result: BulkImportResult }
3010
+ bulkImportCompanies: {
3011
+ params: Omit<BulkImportCompaniesParams, 'organizationId'>
3012
+ result: BulkImportCompaniesResult
3013
+ }
3014
+ deactivateContactsByCompany: {
3015
+ params: { companyId: string }
3016
+ result: { deactivated: number }
3017
+ }
3018
+ // Deal operations
3019
+ upsertDeal: { params: Omit<UpsertDealParams, 'organizationId'>; result: AcqDeal }
3020
+ getDealByEmail: { params: { email: string }; result: AcqDeal | null }
3021
+ getDealByEnvelopeId: { params: { envelopeId: string }; result: AcqDeal | null }
3022
+ updateDealEnvelopeId: { params: { dealId: string; envelopeId: string }; result: AcqDeal | null }
3023
+ getDealById: { params: Omit<GetDealByIdParams, 'organizationId'>; result: AcqDeal | null }
3024
+ getContactById: { params: Omit<GetContactByIdParams, 'organizationId'>; result: AcqContact | null }
3025
+ getCompanyById: { params: Omit<GetCompanyByIdParams, 'organizationId'>; result: AcqCompany | null }
3026
+ // Deal-sync operations
3027
+ updateDiscoveryData: { params: Omit<UpdateDiscoveryDataParams, 'organizationId'>; result: void }
3028
+ updateProposalData: { params: Omit<UpdateProposalDataParams, 'organizationId'>; result: void }
3029
+ markProposalSent: { params: Omit<MarkProposalSentParams, 'organizationId'>; result: void }
3030
+ markProposalReviewed: { params: Omit<MarkProposalReviewedParams, 'organizationId'>; result: void }
3031
+ updateCloseLostReason: { params: Omit<UpdateCloseLostReasonParams, 'organizationId'>; result: void }
3032
+ updateFees: { params: Omit<UpdateFeesParams, 'organizationId'>; result: void }
3033
+ transitionItem: { params: Omit<TransitionItemParams, 'organizationId'>; result: void }
3034
+ setContactNurture: { params: Omit<SetContactNurtureParams, 'organizationId'>; result: void }
3035
+ cancelSchedulesAndHitlByEmail: {
3036
+ params: Omit<CancelSchedulesAndHitlByEmailParams, 'organizationId'>
3037
+ result: { schedulesCancelled: number; hitlDeleted: number }
3038
+ }
3039
+ cancelHitlByDealId: { params: Omit<CancelHitlByDealIdParams, 'organizationId'>; result: { hitlDeleted: number } }
3040
+ clearDealFields: { params: Omit<ClearDealFieldsParams, 'organizationId'>; result: void }
3041
+ deleteDeal: { params: Omit<DeleteDealParams, 'organizationId'>; result: void }
3042
+ recordDealActivity: {
3043
+ params: Omit<RecordDealActivityParams, 'organizationId'>
3044
+ result: void
3045
+ }
3046
+ // Deal note operations
3047
+ createDealNote: {
3048
+ params: Omit<CreateDealNoteParams, 'organizationId'>
3049
+ result: AcqDealNote
3050
+ }
3051
+ listDealNotes: {
3052
+ params: Omit<ListDealNotesParams, 'organizationId'>
3053
+ result: AcqDealNote[]
3054
+ }
3055
+ // Deal task operations
3056
+ createDealTask: {
3057
+ params: Omit<CreateDealTaskParams, 'organizationId'>
3058
+ result: AcqDealTask
3059
+ }
3060
+ listDealTasks: {
3061
+ params: Omit<ListDealTasksParams, 'organizationId'>
3062
+ result: AcqDealTask[]
3063
+ }
3064
+ listDealTasksDue: {
3065
+ params: Omit<ListDealTasksDueParams, 'organizationId'>
3066
+ result: AcqDealTask[]
3067
+ }
3068
+ completeDealTask: {
3069
+ params: Omit<CompleteDealTaskParams, 'organizationId'>
3070
+ result: AcqDealTask
3071
+ }
3072
+ // Deal query & analytics operations
3073
+ listDeals: { params: DealFilters; result: AcqDeal[] }
3074
+ getDealPipelineAnalytics: { params: { recentLimit?: number }; result: DealPipelineAnalytics }
3075
+ // Enrichment data operations
3076
+ mergeEnrichmentData: {
3077
+ params: { id: string; table: 'acq_companies' | 'acq_contacts'; data: Record<string, unknown> }
3078
+ result: void
3079
+ }
3080
+ // Social monitoring operations
3081
+ upsertSocialPosts: {
3082
+ params: { posts: Omit<UpsertSocialPostParams, 'organizationId'>[] }
3083
+ result: UpsertSocialPostsResult
3084
+ }
3085
+ setDealStateKey: {
3086
+ params: {
3087
+ dealId: string
3088
+ stateKey: string
3089
+ }
3090
+ result: { ok: true }
3091
+ }
3092
+ // CRM workflow helpers
3093
+ transitionDeal: {
3094
+ params: {
3095
+ dealId: string
3096
+ toStage: string
3097
+ toState?: string
3098
+ }
3099
+ result: { deal: AcqDeal }
3100
+ }
3101
+ loadDeal: {
3102
+ params: { dealId: string }
3103
+ result: DealDetail | null
3104
+ }
3105
+ }
3106
+ ```
3107
+
3108
+ ### `ListToolMap`
3109
+
3110
+ ```typescript
3111
+ export type ListToolMap = {
3112
+ getConfig: {
3113
+ params: { listId: string }
3114
+ result: { scrapingConfig: ScrapingConfig; icp: IcpRubric; pipelineConfig: PipelineConfig }
3115
+ }
3116
+ recordExecution: {
3117
+ params: Omit<RecordListExecutionParams, 'organizationId'>
3118
+ result: void
3119
+ }
3120
+ updateCompanyStage: {
3121
+ params: Omit<UpdateCompanyStageParams, 'organizationId'>
3122
+ result: void
3123
+ }
3124
+ updateContactStage: {
3125
+ params: Omit<UpdateContactStageParams, 'organizationId'>
3126
+ result: void
3127
+ }
3128
+ }
3129
+ ```