@elevasis/core 0.13.0 → 0.15.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 (42) 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 +2336 -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 +317 -73
  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 +101 -78
  16. package/src/business/acquisition/index.ts +51 -9
  17. package/src/business/acquisition/stateful.ts +30 -0
  18. package/src/business/acquisition/types.ts +48 -80
  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 +934 -874
  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 +30 -2
  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.test.ts +189 -0
  37. package/src/organization-model/domains/sales.ts +456 -94
  38. package/src/organization-model/published.ts +21 -21
  39. package/src/organization-model/resolve.ts +21 -8
  40. package/src/platform/constants/versions.ts +1 -1
  41. package/src/reference/_generated/contracts.md +2336 -0
  42. package/src/supabase/database.types.ts +2958 -2886
@@ -803,3 +803,2339 @@ 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
+ TransitionDealStateRequest: TransitionDealStateRequestSchema,
1270
+ ExecuteActionParams: ExecuteActionParamsSchema,
1271
+ ExecuteActionRequest: ExecuteActionRequestSchema,
1272
+
1273
+ // Responses
1274
+ DealListResponse: DealListResponseSchema,
1275
+ DealSummaryResponse: DealSummaryResponseSchema,
1276
+ DealLookupResponse: DealLookupResponseSchema,
1277
+ DealDetailResponse: DealDetailResponseSchema,
1278
+ DealNoteResponse: DealNoteResponseSchema,
1279
+ DealNoteListResponse: DealNoteListResponseSchema,
1280
+ DealTaskResponse: DealTaskResponseSchema,
1281
+ DealTaskListResponse: DealTaskListResponseSchema
1282
+ }
1283
+ ```
1284
+
1285
+ ### `Action`
1286
+
1287
+ ```typescript
1288
+ export interface Action {
1289
+ key: string
1290
+ label: string
1291
+ payloadSchema?: z.ZodTypeAny
1292
+ }
1293
+ ```
1294
+
1295
+ ### `ActionDef`
1296
+
1297
+ ```typescript
1298
+ export interface ActionDef {
1299
+ key: string
1300
+ label: string
1301
+ isAvailableFor: (deal: AcqDealRow) => boolean
1302
+ workflowId: string
1303
+ payloadSchema?: z.ZodTypeAny
1304
+ }
1305
+ ```
1306
+
1307
+ ### `DEFAULT_CRM_ACTIONS`
1308
+
1309
+ ```typescript
1310
+ export const DEFAULT_CRM_ACTIONS: ActionDef[] = [
1311
+ {
1312
+ key: 'move_to_proposal',
1313
+ label: 'Move to Proposal',
1314
+ isAvailableFor: (deal) => deal.stage_key === 'interested',
1315
+ workflowId: 'move_to_proposal-workflow'
1316
+ },
1317
+ {
1318
+ key: 'move_to_closing',
1319
+ label: 'Move to Closing',
1320
+ isAvailableFor: (deal) => deal.stage_key === 'proposal',
1321
+ workflowId: 'move_to_closing-workflow'
1322
+ },
1323
+ {
1324
+ key: 'move_to_closed_won',
1325
+ label: 'Close Won',
1326
+ isAvailableFor: (deal) => deal.stage_key === 'closing',
1327
+ workflowId: 'move_to_closed_won-workflow'
1328
+ },
1329
+ {
1330
+ key: 'move_to_closed_lost',
1331
+ label: 'Close Lost',
1332
+ isAvailableFor: (deal) =>
1333
+ deal.stage_key === 'interested' || deal.stage_key === 'proposal' || deal.stage_key === 'closing',
1334
+ workflowId: 'move_to_closed_lost-workflow'
1335
+ },
1336
+ {
1337
+ key: 'move_to_nurturing',
1338
+ label: 'Move to Nurturing',
1339
+ isAvailableFor: (deal) =>
1340
+ deal.stage_key === 'interested' || deal.stage_key === 'proposal' || deal.stage_key === 'closing',
1341
+ workflowId: 'move_to_nurturing-workflow'
1342
+ },
1343
+ {
1344
+ key: 'send_reply',
1345
+ label: 'Send Reply',
1346
+ isAvailableFor: (deal) =>
1347
+ deal.stage_key === 'interested' &&
1348
+ (deal.state_key === CRM_DISCOVERY_REPLIED_STATE.stateKey ||
1349
+ deal.state_key === CRM_DISCOVERY_LINK_SENT_STATE.stateKey ||
1350
+ deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey),
1351
+ workflowId: 'crm-send-reply-workflow',
1352
+ payloadSchema: SendReplyActionPayloadSchema
1353
+ },
1354
+ {
1355
+ key: 'send_link',
1356
+ label: 'Send Booking Link',
1357
+ isAvailableFor: (deal) =>
1358
+ deal.stage_key === 'interested' && deal.state_key === CRM_DISCOVERY_REPLIED_STATE.stateKey,
1359
+ workflowId: 'crm-send-booking-link-workflow'
1360
+ },
1361
+ {
1362
+ key: 'send_nudge',
1363
+ label: 'Send Nudge',
1364
+ isAvailableFor: (deal) =>
1365
+ deal.stage_key === 'interested' &&
1366
+ (deal.state_key === CRM_DISCOVERY_LINK_SENT_STATE.stateKey ||
1367
+ deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey),
1368
+ workflowId: 'crm-send-nudge-workflow'
1369
+ },
1370
+ {
1371
+ key: 'mark_no_show',
1372
+ label: 'Mark No-Show',
1373
+ isAvailableFor: (deal) =>
1374
+ deal.stage_key === 'interested' && deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey,
1375
+ // Mirrors the auto-timeout precedent in operations/sales/crm/pipeline/timeout-actions.ts:
1376
+ // both manual-click and timeout move the deal to closed_lost. The action_taken activity
1377
+ // event captures operator intent and distinguishes the manual variant from the timed one.
1378
+ workflowId: 'mark_no_show-workflow'
1379
+ },
1380
+ {
1381
+ key: 'rebook',
1382
+ label: 'Rebook',
1383
+ isAvailableFor: (deal) =>
1384
+ deal.stage_key === 'interested' && deal.state_key === CRM_DISCOVERY_BOOKING_CANCELLED_STATE.stateKey,
1385
+ workflowId: 'crm-rebook-workflow'
1386
+ }
1387
+ ]
1388
+ ```
1389
+
1390
+ ### `CrmToolMap`
1391
+
1392
+ ```typescript
1393
+ export type CrmToolMap = {
1394
+ getRecentActivity: { params: CrmRecentActivityParams; result: RecentActivityEntry[] }
1395
+ listDeals: { params: CrmListDealsParams; result: DealListItem[] }
1396
+ getDeal: { params: CrmGetDealParams; result: DealDetail | null }
1397
+ getDealByEmail: { params: CrmGetDealByEmailParams; result: DealDetail | null }
1398
+ createDealNote: { params: CrmDealNoteParams; result: AcqDealNote }
1399
+ listDealNotes: { params: Omit<ListDealNotesParams, 'organizationId'>; result: AcqDealNote[] }
1400
+ createDealTask: { params: CrmDealTaskParams; result: AcqDealTask }
1401
+ listDealTasks: { params: Omit<ListDealTasksParams, 'organizationId'>; result: AcqDealTask[] }
1402
+ listDealTasksDue: { params: CrmTaskDueParams; result: AcqDealTask[] }
1403
+ completeDealTask: { params: Omit<CompleteDealTaskParams, 'organizationId'>; result: AcqDealTask }
1404
+ recordActivity: { params: CrmRecordActivityParams; result: void }
1405
+ deleteDeal: { params: CrmDeleteDealParams; result: void }
1406
+ }
1407
+ ```
1408
+
1409
+ ## Lead Gen Platform Primitives
1410
+
1411
+ ### `WebPost`
1412
+
1413
+ ```typescript
1414
+ /**
1415
+ * Represents a web post from company website scraping.
1416
+ * Used for recent blog posts, news, or announcements.
1417
+ */
1418
+ export interface WebPost {
1419
+ /** ISO date string of when the post was published */
1420
+ date: string
1421
+ /** Title of the web post */
1422
+ title: string
1423
+ /** Brief summary of the post content */
1424
+ summary: string
1425
+ /** AI-generated insight about the post's relevance */
1426
+ aiInsight?: string
1427
+ }
1428
+ ```
1429
+
1430
+ ### `CompanyPipelineStatus`
1431
+
1432
+ ```typescript
1433
+ /**
1434
+ * Tracks pipeline status for a company across all processing stages.
1435
+ */
1436
+ export interface CompanyPipelineStatus {
1437
+ acquired: boolean
1438
+ enrichment: {
1439
+ [source: string]: {
1440
+ status: 'pending' | 'complete' | 'failed' | 'skipped'
1441
+ completedAt?: string
1442
+ error?: string
1443
+ }
1444
+ }
1445
+ }
1446
+ ```
1447
+
1448
+ ### `ContactPipelineStatus`
1449
+
1450
+ ```typescript
1451
+ /**
1452
+ * Tracks pipeline status for a contact across all processing stages.
1453
+ */
1454
+ export interface ContactPipelineStatus {
1455
+ enrichment: {
1456
+ [source: string]: {
1457
+ status: 'pending' | 'complete' | 'failed' | 'skipped'
1458
+ completedAt?: string
1459
+ error?: string
1460
+ }
1461
+ }
1462
+ personalization: {
1463
+ status: 'pending' | 'complete' | 'failed' | 'skipped'
1464
+ completedAt?: string
1465
+ }
1466
+ outreach: {
1467
+ status: 'pending' | 'sent' | 'replied' | 'bounced' | 'opted-out'
1468
+ sentAt?: string
1469
+ channel?: string
1470
+ campaignId?: string
1471
+ }
1472
+ }
1473
+ ```
1474
+
1475
+ ### `CompanyEnrichmentData`
1476
+
1477
+ ```typescript
1478
+ /**
1479
+ * Enrichment data collected for a company from various sources.
1480
+ */
1481
+ export interface CompanyEnrichmentData {
1482
+ googleMaps?: {
1483
+ placeId?: string
1484
+ totalScore?: number
1485
+ reviewsCount?: number
1486
+ address?: string
1487
+ phone?: string
1488
+ categoryName?: string
1489
+ googleMapsUrl?: string
1490
+ scrapedAt?: string
1491
+ }
1492
+ websiteCrawl?: {
1493
+ companyDescription?: string
1494
+ services?: string[]
1495
+ specialties?: string[]
1496
+ staff?: Array<{ name: string; title?: string; email?: string }>
1497
+ automationGaps?: string[]
1498
+ targetAudience?: string
1499
+ category?: string
1500
+ segment?: string
1501
+ recentWin?: string
1502
+ emailCount?: number
1503
+ pageCount?: number
1504
+ totalChars?: number
1505
+ crawledAt?: string
1506
+ extractedAt?: string
1507
+ }
1508
+ website?: {
1509
+ missionVision?: string
1510
+ uniqueAttributes?: string
1511
+ coreOfferings?: string
1512
+ targetAudience?: string
1513
+ companyValues?: string
1514
+ businessDescription?: string
1515
+ recentPosts?: Array<{ date?: string; title?: string; summary?: string; aiInsight?: string }>
1516
+ }
1517
+ tomba?: {
1518
+ waterfallEmail?: {
1519
+ email: string
1520
+ name?: string
1521
+ title?: string
1522
+ department?: string
1523
+ } | null
1524
+ genericEmail?: string | null
1525
+ totalFound?: number
1526
+ searchedAt?: string
1527
+ }
1528
+ }
1529
+ ```
1530
+
1531
+ ### `ContactEnrichmentData`
1532
+
1533
+ ```typescript
1534
+ /**
1535
+ * Enrichment data collected for a contact from various sources.
1536
+ */
1537
+ export interface ContactEnrichmentData {
1538
+ linkedin?: {
1539
+ summary?: string
1540
+ pastExperience?: string
1541
+ education?: string
1542
+ activity?: Array<{ date?: string; content?: string }>
1543
+ }
1544
+ }
1545
+ ```
1546
+
1547
+ ### `AcqList`
1548
+
1549
+ ```typescript
1550
+ export interface AcqList {
1551
+ id: string
1552
+ organizationId: string
1553
+ name: string
1554
+ description: string | null
1555
+ batchIds: string[]
1556
+ instantlyCampaignId: string | null
1557
+ status: ListStatus
1558
+ scrapingConfig: ScrapingConfig
1559
+ icp: IcpRubric
1560
+ pipelineConfig: PipelineConfig
1561
+ metadata: Record<string, unknown>
1562
+ launchedAt: Date | null
1563
+ completedAt: Date | null
1564
+ createdAt: Date
1565
+ }
1566
+ ```
1567
+
1568
+ ### `AcqCompany`
1569
+
1570
+ ```typescript
1571
+ /**
1572
+ * Company record in the acquisition database.
1573
+ * Contains enriched company data from various sources.
1574
+ * Transformed from AcqCompanyRow with camelCase properties.
1575
+ */
1576
+ export interface AcqCompany {
1577
+ id: string
1578
+ organizationId: string
1579
+ name: string
1580
+ domain: string | null
1581
+ linkedinUrl: string | null
1582
+ website: string | null
1583
+ numEmployees: number | null
1584
+ foundedYear: number | null
1585
+ locationCity: string | null
1586
+ locationState: string | null
1587
+ category: string | null
1588
+ categoryPain: string | null
1589
+ segment: string | null
1590
+ pipelineStatus: CompanyPipelineStatus | null
1591
+ enrichmentData: CompanyEnrichmentData | null
1592
+ source: string | null
1593
+ batchId: string | null
1594
+ status: 'active' | 'invalid'
1595
+ verticalResearch: string | null
1596
+ /** Track A: flat qualification score (null until a scoring rubric is defined). Added by W1 migration. */
1597
+ qualificationScore: number | null
1598
+ /** Track A: flat qualification signals jsonb preserving the result payload shape. Added by W1 migration. */
1599
+ qualificationSignals: Record<string, unknown> | null
1600
+ /** Track A: key identifying the rubric used for qualification. Added by W1 migration. */
1601
+ qualificationRubricKey: string | null
1602
+ createdAt: Date
1603
+ updatedAt: Date
1604
+ }
1605
+ ```
1606
+
1607
+ ### `AcqContact`
1608
+
1609
+ ```typescript
1610
+ /**
1611
+ * Contact record in the acquisition database.
1612
+ * Contains enriched contact data and personalization content.
1613
+ * Transformed from AcqContactRow with camelCase properties.
1614
+ */
1615
+ export interface AcqContact {
1616
+ id: string
1617
+ organizationId: string
1618
+ companyId: string | null
1619
+ email: string
1620
+ emailValid: 'VALID' | 'INVALID' | 'RISKY' | 'UNKNOWN' | null
1621
+ firstName: string | null
1622
+ lastName: string | null
1623
+ linkedinUrl: string | null
1624
+ title: string | null
1625
+ headline: string | null
1626
+ filterReason: string | null
1627
+ openingLine: string | null
1628
+ source: string | null
1629
+ sourceId: string | null
1630
+ pipelineStatus: ContactPipelineStatus | null
1631
+ enrichmentData: ContactEnrichmentData | null
1632
+ /** Attio Person record ID - set when contact responds and is added to CRM */
1633
+ attioPersonId: string | null
1634
+ batchId: string | null
1635
+ status: 'active' | 'invalid'
1636
+ createdAt: Date
1637
+ updatedAt: Date
1638
+ }
1639
+ ```
1640
+
1641
+ ### `ListTelemetry`
1642
+
1643
+ ```typescript
1644
+ /**
1645
+ * Live-scan aggregate telemetry for a single list, computed on demand from
1646
+ * the list junction tables and current contact deliverability state.
1647
+ * `stageCounts` are attempted counts from list-row processing_state.
1648
+ */
1649
+ export interface ListTelemetry {
1650
+ listId: string
1651
+ totalCompanies: number
1652
+ totalContacts: number
1653
+ stageCounts: {
1654
+ populated: number
1655
+ extracted: number
1656
+ qualified: number
1657
+ discovered: number
1658
+ verified: number
1659
+ personalized: number
1660
+ uploaded: number
1661
+ }
1662
+ deliverability: {
1663
+ valid: number
1664
+ risky: number
1665
+ invalid: number
1666
+ unknown: number
1667
+ bounced: number
1668
+ }
1669
+ /** Reserved -- active workflow IDs associated with this list. */
1670
+ activeWorkflows?: string[]
1671
+ }
1672
+ ```
1673
+
1674
+ ### `ListStageCountsSchema`
1675
+
1676
+ ```typescript
1677
+ export const ListStageCountsSchema = z.object({
1678
+ // Attempted counts by canonical lead-gen stage. The detailed status
1679
+ // distribution lives on ListProgress; telemetry keeps the overview payload small.
1680
+ stageCounts: z.object({
1681
+ populated: z.number().int(),
1682
+ extracted: z.number().int(),
1683
+ qualified: z.number().int(),
1684
+ discovered: z.number().int(),
1685
+ verified: z.number().int(),
1686
+ personalized: z.number().int(),
1687
+ uploaded: z.number().int()
1688
+ }),
1689
+ deliverability: z.object({
1690
+ valid: z.number().int(),
1691
+ risky: z.number().int(),
1692
+ invalid: z.number().int(),
1693
+ unknown: z.number().int(),
1694
+ bounced: z.number().int()
1695
+ })
1696
+ })
1697
+ ```
1698
+
1699
+ ### `ListTelemetrySchema`
1700
+
1701
+ ```typescript
1702
+ export const ListTelemetrySchema = z.object({
1703
+ listId: UuidSchema,
1704
+ totalCompanies: z.number().int(),
1705
+ totalContacts: z.number().int(),
1706
+ stageCounts: ListStageCountsSchema.shape.stageCounts,
1707
+ deliverability: ListStageCountsSchema.shape.deliverability,
1708
+ activeWorkflows: z.array(z.string()).optional()
1709
+ })
1710
+ ```
1711
+
1712
+ ### `ListIdParamsSchema`
1713
+
1714
+ ```typescript
1715
+ export const ListIdParamsSchema = z.object({
1716
+ listId: UuidSchema
1717
+ })
1718
+ ```
1719
+
1720
+ ### `CreateListRequestSchema`
1721
+
1722
+ ```typescript
1723
+ export const CreateListRequestSchema = z
1724
+ .object({
1725
+ name: z.string().trim().min(1).max(255),
1726
+ description: z.string().trim().nullable().optional(),
1727
+ status: ListStatusSchema.optional(),
1728
+ scrapingConfig: ScrapingConfigSchema.optional(),
1729
+ icp: IcpRubricSchema.optional(),
1730
+ pipelineConfig: PipelineConfigSchema.optional()
1731
+ })
1732
+ .strict()
1733
+ ```
1734
+
1735
+ ### `UpdateListRequestSchema`
1736
+
1737
+ ```typescript
1738
+ export const UpdateListRequestSchema = z
1739
+ .object({
1740
+ name: z.string().trim().min(1).max(255).optional(),
1741
+ description: z.string().trim().nullable().optional(),
1742
+ batchIds: z.array(z.string()).optional()
1743
+ })
1744
+ .strict()
1745
+ .refine((data) => data.name !== undefined || data.description !== undefined || data.batchIds !== undefined, {
1746
+ message: 'At least one field (name, description, or batchIds) must be provided'
1747
+ })
1748
+ ```
1749
+
1750
+ ### `UpdateListConfigRequestSchema`
1751
+
1752
+ ```typescript
1753
+ /**
1754
+ * Partial patch for the three jsonb config columns. UI sends only the edited
1755
+ * subtree; server writes the field as-is (no deep merge — each column is
1756
+ * replaced atomically when present in the patch).
1757
+ */
1758
+ export const UpdateListConfigRequestSchema = z
1759
+ .object({
1760
+ scrapingConfig: ScrapingConfigSchema.partial().optional(),
1761
+ icp: IcpRubricSchema.partial().optional(),
1762
+ pipelineConfig: PipelineConfigSchema.partial().optional()
1763
+ })
1764
+ .strict()
1765
+ .refine((data) => data.scrapingConfig !== undefined || data.icp !== undefined || data.pipelineConfig !== undefined, {
1766
+ message: 'At least one of scrapingConfig, icp, or pipelineConfig must be provided'
1767
+ })
1768
+ ```
1769
+
1770
+ ### `AddCompaniesToListRequestSchema`
1771
+
1772
+ ```typescript
1773
+ export const AddCompaniesToListRequestSchema = z
1774
+ .object({
1775
+ companyIds: z.array(UuidSchema).min(1).max(1000)
1776
+ })
1777
+ .strict()
1778
+ ```
1779
+
1780
+ ### `RemoveCompaniesFromListRequestSchema`
1781
+
1782
+ ```typescript
1783
+ export const RemoveCompaniesFromListRequestSchema = z
1784
+ .object({
1785
+ companyIds: z.array(UuidSchema).min(1).max(1000)
1786
+ })
1787
+ .strict()
1788
+ ```
1789
+
1790
+ ### `AddContactsToListRequestSchema`
1791
+
1792
+ ```typescript
1793
+ export const AddContactsToListRequestSchema = z
1794
+ .object({
1795
+ contactIds: z.array(UuidSchema).min(1).max(1000)
1796
+ })
1797
+ .strict()
1798
+ ```
1799
+
1800
+ ### `RecordListExecutionRequestSchema`
1801
+
1802
+ ```typescript
1803
+ export const RecordListExecutionRequestSchema = z
1804
+ .object({
1805
+ executionId: UuidSchema,
1806
+ configSnapshot: z.record(z.string(), z.unknown()).optional()
1807
+ })
1808
+ .strict()
1809
+ ```
1810
+
1811
+ ### `AcqListResponseSchema`
1812
+
1813
+ ```typescript
1814
+ /**
1815
+ * Single list as returned by /api/acquisition/lists/:id etc.
1816
+ * Camel-cased domain shape matching AcqList in types.ts.
1817
+ */
1818
+ export const AcqListResponseSchema = z.object({
1819
+ id: z.string(),
1820
+ organizationId: z.string(),
1821
+ name: z.string(),
1822
+ description: z.string().nullable(),
1823
+ type: z.string(),
1824
+ batchIds: z.array(z.string()),
1825
+ instantlyCampaignId: z.string().nullable(),
1826
+ /** Lifecycle status (draft | enriching | launched | closing | archived). */
1827
+ status: ListStatusSchema,
1828
+ metadata: z.record(z.string(), z.unknown()),
1829
+ launchedAt: z.string().nullable(),
1830
+ completedAt: z.string().nullable(),
1831
+ createdAt: z.string(),
1832
+ /** Scraping criteria stored as jsonb on the row. */
1833
+ scrapingConfig: ScrapingConfigSchema,
1834
+ /** ICP / qualification rubric stored as jsonb on the row. */
1835
+ icp: IcpRubricSchema,
1836
+ /** Pipeline presentation contract stored as jsonb on the row. */
1837
+ pipelineConfig: PipelineConfigSchema
1838
+ })
1839
+ ```
1840
+
1841
+ ### `AcqListListResponseSchema`
1842
+
1843
+ ```typescript
1844
+ export const AcqListListResponseSchema = z.array(AcqListResponseSchema)
1845
+ ```
1846
+
1847
+ ### `ListTelemetryResponseSchema`
1848
+
1849
+ ```typescript
1850
+ export const ListTelemetryResponseSchema = ListTelemetrySchema
1851
+ ```
1852
+
1853
+ ### `ListTelemetryListResponseSchema`
1854
+
1855
+ ```typescript
1856
+ export const ListTelemetryListResponseSchema = z.array(ListTelemetrySchema)
1857
+ ```
1858
+
1859
+ ### `ListExecutionSummarySchema`
1860
+
1861
+ ```typescript
1862
+ /**
1863
+ * Row from acq_list_executions joined with the execution summary,
1864
+ * shaped for the /lists/:id/executions response.
1865
+ */
1866
+ export const ListExecutionSummarySchema = z.object({
1867
+ executionId: z.string(),
1868
+ resourceId: z.string(),
1869
+ status: z.string(),
1870
+ createdAt: z.string(),
1871
+ completedAt: z.string().nullable(),
1872
+ durationMs: z.number().int().nullable()
1873
+ })
1874
+ ```
1875
+
1876
+ ### `ListExecutionsResponseSchema`
1877
+
1878
+ ```typescript
1879
+ export const ListExecutionsResponseSchema = z.array(ListExecutionSummarySchema)
1880
+ ```
1881
+
1882
+ ### `AcqCompanyStatusSchema`
1883
+
1884
+ ```typescript
1885
+ export const AcqCompanyStatusSchema = z.enum(['active', 'invalid'])
1886
+ ```
1887
+
1888
+ ### `AcqContactStatusSchema`
1889
+
1890
+ ```typescript
1891
+ export const AcqContactStatusSchema = z.enum(['active', 'invalid'])
1892
+ ```
1893
+
1894
+ ### `AcqEmailValidSchema`
1895
+
1896
+ ```typescript
1897
+ export const AcqEmailValidSchema = z.enum(['VALID', 'INVALID', 'RISKY', 'UNKNOWN'])
1898
+ ```
1899
+
1900
+ ### `CompanyIdParamsSchema`
1901
+
1902
+ ```typescript
1903
+ export const CompanyIdParamsSchema = z.object({
1904
+ companyId: UuidSchema
1905
+ })
1906
+ ```
1907
+
1908
+ ### `ContactIdParamsSchema`
1909
+
1910
+ ```typescript
1911
+ export const ContactIdParamsSchema = z.object({
1912
+ contactId: UuidSchema
1913
+ })
1914
+ ```
1915
+
1916
+ ### `ListCompaniesQuerySchema`
1917
+
1918
+ ```typescript
1919
+ export const ListCompaniesQuerySchema = z
1920
+ .object({
1921
+ search: z.string().trim().min(1).max(200).optional(),
1922
+ listId: UuidSchema.optional(),
1923
+ domain: z.string().trim().min(1).max(255).optional(),
1924
+ website: z.string().trim().min(1).max(2048).optional(),
1925
+ segment: z.string().trim().min(1).max(255).optional(),
1926
+ category: z.string().trim().min(1).max(255).optional(),
1927
+ batchId: z.string().trim().min(1).max(255).optional(),
1928
+ status: AcqCompanyStatusSchema.optional(),
1929
+ includeAll: QueryBooleanSchema.optional(),
1930
+ limit: z.coerce.number().int().min(1).max(5000).default(50),
1931
+ offset: z.coerce.number().int().min(0).default(0)
1932
+ })
1933
+ .strict()
1934
+ ```
1935
+
1936
+ ### `ListContactsQuerySchema`
1937
+
1938
+ ```typescript
1939
+ export const ListContactsQuerySchema = z
1940
+ .object({
1941
+ search: z.string().trim().min(1).max(200).optional(),
1942
+ listId: UuidSchema.optional(),
1943
+ openingLineIsNull: QueryBooleanSchema.optional(),
1944
+ batchId: z.string().trim().min(1).max(255).optional(),
1945
+ contactStatus: AcqContactStatusSchema.optional(),
1946
+ limit: z.coerce.number().int().min(1).max(5000).default(5000),
1947
+ offset: z.coerce.number().int().min(0).default(0)
1948
+ })
1949
+ .strict()
1950
+ ```
1951
+
1952
+ ### `CreateCompanyRequestSchema`
1953
+
1954
+ ```typescript
1955
+ export const CreateCompanyRequestSchema = z
1956
+ .object({
1957
+ name: z.string().trim().min(1).max(255),
1958
+ domain: z.string().trim().min(1).max(255).optional(),
1959
+ linkedinUrl: z.string().trim().url().optional(),
1960
+ website: z.string().trim().url().optional(),
1961
+ numEmployees: z.number().int().min(0).optional(),
1962
+ foundedYear: z.number().int().optional(),
1963
+ locationCity: z.string().trim().min(1).max(255).optional(),
1964
+ locationState: z.string().trim().min(1).max(255).optional(),
1965
+ category: z.string().trim().min(1).max(255).optional(),
1966
+ source: z.string().trim().min(1).max(255).optional(),
1967
+ batchId: z.string().trim().min(1).max(255).optional(),
1968
+ verticalResearch: z.string().trim().min(1).max(5000).optional()
1969
+ })
1970
+ .strict()
1971
+ ```
1972
+
1973
+ ### `UpdateCompanyRequestSchema`
1974
+
1975
+ ```typescript
1976
+ export const UpdateCompanyRequestSchema = z
1977
+ .object({
1978
+ name: z.string().trim().min(1).max(255).optional(),
1979
+ domain: z.string().trim().min(1).max(255).optional(),
1980
+ linkedinUrl: z.string().trim().url().optional(),
1981
+ website: z.string().trim().url().optional(),
1982
+ numEmployees: z.number().int().min(0).optional(),
1983
+ foundedYear: z.number().int().optional(),
1984
+ locationCity: z.string().trim().min(1).max(255).optional(),
1985
+ locationState: z.string().trim().min(1).max(255).optional(),
1986
+ category: z.string().trim().min(1).max(255).optional(),
1987
+ segment: z.string().trim().min(1).max(255).optional(),
1988
+ pipelineStatus: z.record(z.string(), z.unknown()).optional(),
1989
+ enrichmentData: z.record(z.string(), z.unknown()).optional(),
1990
+ source: z.string().trim().min(1).max(255).optional(),
1991
+ batchId: z.string().trim().min(1).max(255).optional(),
1992
+ status: AcqCompanyStatusSchema.optional(),
1993
+ verticalResearch: z.string().trim().min(1).max(5000).nullable().optional()
1994
+ })
1995
+ .strict()
1996
+ .refine(
1997
+ (data) =>
1998
+ data.name !== undefined ||
1999
+ data.domain !== undefined ||
2000
+ data.linkedinUrl !== undefined ||
2001
+ data.website !== undefined ||
2002
+ data.numEmployees !== undefined ||
2003
+ data.foundedYear !== undefined ||
2004
+ data.locationCity !== undefined ||
2005
+ data.locationState !== undefined ||
2006
+ data.category !== undefined ||
2007
+ data.segment !== undefined ||
2008
+ data.pipelineStatus !== undefined ||
2009
+ data.enrichmentData !== undefined ||
2010
+ data.source !== undefined ||
2011
+ data.batchId !== undefined ||
2012
+ data.status !== undefined ||
2013
+ data.verticalResearch !== undefined,
2014
+ {
2015
+ message: 'At least one field must be provided'
2016
+ }
2017
+ )
2018
+ ```
2019
+
2020
+ ### `CreateContactRequestSchema`
2021
+
2022
+ ```typescript
2023
+ export const CreateContactRequestSchema = z
2024
+ .object({
2025
+ email: z.string().trim().email(),
2026
+ companyId: UuidSchema.optional(),
2027
+ firstName: z.string().trim().min(1).max(255).optional(),
2028
+ lastName: z.string().trim().min(1).max(255).optional(),
2029
+ linkedinUrl: z.string().trim().url().optional(),
2030
+ title: z.string().trim().min(1).max(255).optional(),
2031
+ source: z.string().trim().min(1).max(255).optional(),
2032
+ sourceId: z.string().trim().min(1).max(255).optional(),
2033
+ batchId: z.string().trim().min(1).max(255).optional()
2034
+ })
2035
+ .strict()
2036
+ ```
2037
+
2038
+ ### `UpdateContactRequestSchema`
2039
+
2040
+ ```typescript
2041
+ export const UpdateContactRequestSchema = z
2042
+ .object({
2043
+ companyId: UuidSchema.optional(),
2044
+ emailValid: AcqEmailValidSchema.optional(),
2045
+ firstName: z.string().trim().min(1).max(255).optional(),
2046
+ lastName: z.string().trim().min(1).max(255).optional(),
2047
+ linkedinUrl: z.string().trim().url().optional(),
2048
+ title: z.string().trim().min(1).max(255).optional(),
2049
+ headline: z.string().trim().min(1).max(5000).optional(),
2050
+ filterReason: z.string().trim().min(1).max(5000).optional(),
2051
+ openingLine: z.string().trim().min(1).max(5000).optional(),
2052
+ pipelineStatus: z.record(z.string(), z.unknown()).optional(),
2053
+ enrichmentData: z.record(z.string(), z.unknown()).optional(),
2054
+ status: AcqContactStatusSchema.optional()
2055
+ })
2056
+ .strict()
2057
+ .refine(
2058
+ (data) =>
2059
+ data.companyId !== undefined ||
2060
+ data.emailValid !== undefined ||
2061
+ data.firstName !== undefined ||
2062
+ data.lastName !== undefined ||
2063
+ data.linkedinUrl !== undefined ||
2064
+ data.title !== undefined ||
2065
+ data.headline !== undefined ||
2066
+ data.filterReason !== undefined ||
2067
+ data.openingLine !== undefined ||
2068
+ data.pipelineStatus !== undefined ||
2069
+ data.enrichmentData !== undefined ||
2070
+ data.status !== undefined,
2071
+ {
2072
+ message: 'At least one field must be provided'
2073
+ }
2074
+ )
2075
+ ```
2076
+
2077
+ ### `AcqCompanyResponseSchema`
2078
+
2079
+ ```typescript
2080
+ export const AcqCompanyResponseSchema = z.object({
2081
+ id: z.string(),
2082
+ organizationId: z.string(),
2083
+ name: z.string(),
2084
+ domain: z.string().nullable(),
2085
+ linkedinUrl: z.string().nullable(),
2086
+ website: z.string().nullable(),
2087
+ numEmployees: z.number().nullable(),
2088
+ foundedYear: z.number().nullable(),
2089
+ locationCity: z.string().nullable(),
2090
+ locationState: z.string().nullable(),
2091
+ category: z.string().nullable(),
2092
+ categoryPain: z.string().nullable(),
2093
+ segment: z.string().nullable(),
2094
+ pipelineStatus: z.record(z.string(), z.unknown()).nullable(),
2095
+ enrichmentData: z.record(z.string(), z.unknown()).nullable(),
2096
+ source: z.string().nullable(),
2097
+ batchId: z.string().nullable(),
2098
+ status: AcqCompanyStatusSchema,
2099
+ contactCount: z.number().int().min(0),
2100
+ verticalResearch: z.string().nullable(),
2101
+ createdAt: z.string(),
2102
+ updatedAt: z.string()
2103
+ })
2104
+ ```
2105
+
2106
+ ### `AcqCompanyListResponseSchema`
2107
+
2108
+ ```typescript
2109
+ export const AcqCompanyListResponseSchema = z.object({
2110
+ data: z.array(AcqCompanyResponseSchema),
2111
+ total: z.number().int(),
2112
+ limit: z.number().int(),
2113
+ offset: z.number().int()
2114
+ })
2115
+ ```
2116
+
2117
+ ### `AcqCompanyFacetsResponseSchema`
2118
+
2119
+ ```typescript
2120
+ export const AcqCompanyFacetsResponseSchema = z.object({
2121
+ segments: z.array(z.string()),
2122
+ categories: z.array(z.string()),
2123
+ statuses: z.array(AcqCompanyStatusSchema)
2124
+ })
2125
+ ```
2126
+
2127
+ ### `AcqContactCompanySummarySchema`
2128
+
2129
+ ```typescript
2130
+ export const AcqContactCompanySummarySchema = z.object({
2131
+ id: z.string(),
2132
+ name: z.string(),
2133
+ domain: z.string().nullable(),
2134
+ website: z.string().nullable(),
2135
+ linkedinUrl: z.string().nullable(),
2136
+ segment: z.string().nullable(),
2137
+ category: z.string().nullable(),
2138
+ status: AcqCompanyStatusSchema
2139
+ })
2140
+ ```
2141
+
2142
+ ### `AcqContactResponseSchema`
2143
+
2144
+ ```typescript
2145
+ export const AcqContactResponseSchema = z.object({
2146
+ id: z.string(),
2147
+ organizationId: z.string(),
2148
+ companyId: z.string().nullable(),
2149
+ email: z.string(),
2150
+ emailValid: AcqEmailValidSchema.nullable(),
2151
+ firstName: z.string().nullable(),
2152
+ lastName: z.string().nullable(),
2153
+ linkedinUrl: z.string().nullable(),
2154
+ title: z.string().nullable(),
2155
+ headline: z.string().nullable(),
2156
+ filterReason: z.string().nullable(),
2157
+ openingLine: z.string().nullable(),
2158
+ source: z.string().nullable(),
2159
+ sourceId: z.string().nullable(),
2160
+ pipelineStatus: z.record(z.string(), z.unknown()).nullable(),
2161
+ enrichmentData: z.record(z.string(), z.unknown()).nullable(),
2162
+ attioPersonId: z.string().nullable(),
2163
+ batchId: z.string().nullable(),
2164
+ status: AcqContactStatusSchema,
2165
+ company: AcqContactCompanySummarySchema.nullable().optional(),
2166
+ createdAt: z.string(),
2167
+ updatedAt: z.string()
2168
+ })
2169
+ ```
2170
+
2171
+ ### `AcqContactListResponseSchema`
2172
+
2173
+ ```typescript
2174
+ export const AcqContactListResponseSchema = z.object({
2175
+ data: z.array(AcqContactResponseSchema),
2176
+ total: z.number().int(),
2177
+ limit: z.number().int(),
2178
+ offset: z.number().int()
2179
+ })
2180
+ ```
2181
+
2182
+ ### `AcqArtifactOwnerKindSchema`
2183
+
2184
+ ```typescript
2185
+ export const AcqArtifactOwnerKindSchema = z.enum(['company', 'contact', 'deal', 'list', 'list_member'])
2186
+ ```
2187
+
2188
+ ### `ListArtifactsQuerySchema`
2189
+
2190
+ ```typescript
2191
+ export const ListArtifactsQuerySchema = z
2192
+ .object({
2193
+ ownerKind: AcqArtifactOwnerKindSchema,
2194
+ ownerId: UuidSchema
2195
+ })
2196
+ .strict()
2197
+ ```
2198
+
2199
+ ### `CreateArtifactRequestSchema`
2200
+
2201
+ ```typescript
2202
+ export const CreateArtifactRequestSchema = z
2203
+ .object({
2204
+ ownerKind: AcqArtifactOwnerKindSchema,
2205
+ ownerId: UuidSchema,
2206
+ kind: z.string().trim().min(1).max(255),
2207
+ content: z.record(z.string(), z.unknown()),
2208
+ sourceExecutionId: UuidSchema.optional()
2209
+ })
2210
+ .strict()
2211
+ ```
2212
+
2213
+ ### `AcqArtifactResponseSchema`
2214
+
2215
+ ```typescript
2216
+ export const AcqArtifactResponseSchema = z.object({
2217
+ id: z.string(),
2218
+ organizationId: z.string(),
2219
+ ownerKind: z.string(),
2220
+ ownerId: z.string(),
2221
+ kind: z.string(),
2222
+ content: z.record(z.string(), z.unknown()),
2223
+ sourceExecutionId: z.string().nullable(),
2224
+ createdBy: z.string().nullable(),
2225
+ createdAt: z.string(),
2226
+ version: z.number().int()
2227
+ })
2228
+ ```
2229
+
2230
+ ### `AcqArtifactListResponseSchema`
2231
+
2232
+ ```typescript
2233
+ export const AcqArtifactListResponseSchema = z.object({
2234
+ artifacts: z.array(AcqArtifactResponseSchema)
2235
+ })
2236
+ ```
2237
+
2238
+ ### `ListMembersQuerySchema`
2239
+
2240
+ ```typescript
2241
+ export const ListMembersQuerySchema = z
2242
+ .object({
2243
+ limit: z.coerce.number().int().min(1).max(500).default(50),
2244
+ offset: z.coerce.number().int().min(0).default(0)
2245
+ })
2246
+ .strict()
2247
+ ```
2248
+
2249
+ ### `MemberIdParamsSchema`
2250
+
2251
+ ```typescript
2252
+ export const MemberIdParamsSchema = z.object({
2253
+ memberId: UuidSchema
2254
+ })
2255
+ ```
2256
+
2257
+ ### `AcqListMemberContactSummarySchema`
2258
+
2259
+ ```typescript
2260
+ export const AcqListMemberContactSummarySchema = z.object({
2261
+ id: z.string(),
2262
+ email: z.string(),
2263
+ firstName: z.string().nullable(),
2264
+ lastName: z.string().nullable(),
2265
+ title: z.string().nullable(),
2266
+ linkedinUrl: z.string().nullable(),
2267
+ companyId: z.string().nullable()
2268
+ })
2269
+ ```
2270
+
2271
+ ### `AcqListMemberResponseSchema`
2272
+
2273
+ ```typescript
2274
+ export const AcqListMemberResponseSchema = z.object({
2275
+ id: z.string(),
2276
+ listId: z.string(),
2277
+ contactId: z.string(),
2278
+ pipelineKey: z.string(),
2279
+ stageKey: z.string(),
2280
+ stateKey: z.string(),
2281
+ activityLog: z.unknown(),
2282
+ addedAt: z.string(),
2283
+ addedBy: z.string().nullable(),
2284
+ sourceExecutionId: z.string().nullable(),
2285
+ contact: AcqListMemberContactSummarySchema.nullable()
2286
+ })
2287
+ ```
2288
+
2289
+ ### `AcqListMembersResponseSchema`
2290
+
2291
+ ```typescript
2292
+ export const AcqListMembersResponseSchema = z.object({
2293
+ members: z.array(AcqListMemberResponseSchema)
2294
+ })
2295
+ ```
2296
+
2297
+ ### `ListCompanyIdParamsSchema`
2298
+
2299
+ ```typescript
2300
+ export const ListCompanyIdParamsSchema = z.object({
2301
+ listCompanyId: UuidSchema
2302
+ })
2303
+ ```
2304
+
2305
+ ### `AcqListCompanyResponseSchema`
2306
+
2307
+ ```typescript
2308
+ export const AcqListCompanyResponseSchema = z.object({
2309
+ id: z.string(),
2310
+ listId: z.string(),
2311
+ companyId: z.string(),
2312
+ pipelineKey: z.string(),
2313
+ stageKey: z.string(),
2314
+ stateKey: z.string(),
2315
+ activityLog: z.unknown(),
2316
+ addedAt: z.string(),
2317
+ addedBy: z.string().nullable(),
2318
+ sourceExecutionId: z.string().nullable()
2319
+ })
2320
+ ```
2321
+
2322
+ ### `AcqCompanySchemas`
2323
+
2324
+ ```typescript
2325
+ export const AcqCompanySchemas = {
2326
+ CompanyIdParams: CompanyIdParamsSchema,
2327
+ ListCompaniesQuery: ListCompaniesQuerySchema,
2328
+ CreateCompanyRequest: CreateCompanyRequestSchema,
2329
+ UpdateCompanyRequest: UpdateCompanyRequestSchema,
2330
+ AcqCompanyResponse: AcqCompanyResponseSchema,
2331
+ AcqCompanyListResponse: AcqCompanyListResponseSchema,
2332
+ AcqCompanyFacetsResponse: AcqCompanyFacetsResponseSchema
2333
+ }
2334
+ ```
2335
+
2336
+ ### `AcqContactSchemas`
2337
+
2338
+ ```typescript
2339
+ export const AcqContactSchemas = {
2340
+ ContactIdParams: ContactIdParamsSchema,
2341
+ ListContactsQuery: ListContactsQuerySchema,
2342
+ CreateContactRequest: CreateContactRequestSchema,
2343
+ UpdateContactRequest: UpdateContactRequestSchema,
2344
+ AcqContactResponse: AcqContactResponseSchema,
2345
+ AcqContactListResponse: AcqContactListResponseSchema
2346
+ }
2347
+ ```
2348
+
2349
+ ### `AcqListSchemas`
2350
+
2351
+ ```typescript
2352
+ export const AcqListSchemas = {
2353
+ // Params
2354
+ ListIdParams: ListIdParamsSchema,
2355
+
2356
+ // Primitives (for UI / tests)
2357
+ ListStatus: ListStatusSchema,
2358
+ ScrapingConfig: ScrapingConfigSchema,
2359
+ IcpRubric: IcpRubricSchema,
2360
+ PipelineConfig: PipelineConfigSchema,
2361
+ PipelineStage: PipelineStageSchema,
2362
+ ProcessingStageStatus: ProcessingStageStatusSchema,
2363
+ ListStageCounts: ListStageCountsSchema,
2364
+ ListTelemetry: ListTelemetrySchema,
2365
+
2366
+ // Requests
2367
+ CreateListRequest: CreateListRequestSchema,
2368
+ UpdateListRequest: UpdateListRequestSchema,
2369
+ UpdateListStatusRequest: UpdateListStatusRequestSchema,
2370
+ UpdateListConfigRequest: UpdateListConfigRequestSchema,
2371
+ AddCompaniesToListRequest: AddCompaniesToListRequestSchema,
2372
+ RemoveCompaniesFromListRequest: RemoveCompaniesFromListRequestSchema,
2373
+ AddContactsToListRequest: AddContactsToListRequestSchema,
2374
+ RecordListExecutionRequest: RecordListExecutionRequestSchema,
2375
+
2376
+ // Responses
2377
+ AcqListResponse: AcqListResponseSchema,
2378
+ AcqListListResponse: AcqListListResponseSchema,
2379
+ ListTelemetryResponse: ListTelemetryResponseSchema,
2380
+ ListTelemetryListResponse: ListTelemetryListResponseSchema,
2381
+ ListExecutionsResponse: ListExecutionsResponseSchema,
2382
+ ListProgressResponse: ListProgressResponseSchema
2383
+ }
2384
+ ```
2385
+
2386
+ ### `AcqSubstrateSchemas`
2387
+
2388
+ ```typescript
2389
+ export const AcqSubstrateSchemas = {
2390
+ // Artifacts
2391
+ ListArtifactsQuery: ListArtifactsQuerySchema,
2392
+ CreateArtifactRequest: CreateArtifactRequestSchema,
2393
+ AcqArtifactResponse: AcqArtifactResponseSchema,
2394
+ AcqArtifactListResponse: AcqArtifactListResponseSchema,
2395
+
2396
+ // List members
2397
+ ListMembersQuery: ListMembersQuerySchema,
2398
+ MemberIdParams: MemberIdParamsSchema,
2399
+ AcqListMemberResponse: AcqListMemberResponseSchema,
2400
+ AcqListMembersResponse: AcqListMembersResponseSchema,
2401
+
2402
+ // List companies
2403
+ ListCompanyIdParams: ListCompanyIdParamsSchema,
2404
+ AcqListCompanyResponse: AcqListCompanyResponseSchema,
2405
+
2406
+ // Transition (shared with deals — TransitionItemRequestSchema)
2407
+ TransitionItemRequest: TransitionItemRequestSchema
2408
+ }
2409
+ ```
2410
+
2411
+ ### `Stateful`
2412
+
2413
+ ```typescript
2414
+ /**
2415
+ * Stateful trait — the (pipeline_key, stage_key, state_key, activity_log) quartet
2416
+ * applied to acq_deals (CRM HITL, shipped 2026-04-27) and being generalized to
2417
+ * acq_lists / acq_list_members / acq_list_companies via Track B.
2418
+ */
2419
+ export interface Stateful {
2420
+ pipeline_key: string
2421
+ stage_key: string
2422
+ state_key: string
2423
+ activity_log: ActivityEvent[]
2424
+ }
2425
+ ```
2426
+
2427
+ ### `TransitionItem`
2428
+
2429
+ ```typescript
2430
+ /** Generic transition shape — concrete per-entity transitionItem implementations satisfy this. */
2431
+ export type TransitionItem<T extends Stateful, TEvent extends ActivityEvent> = (
2432
+ item: T,
2433
+ transition: { stage_key?: string; state_key?: string; event: TEvent }
2434
+ ) => T
2435
+ ```
2436
+
2437
+ ### `DeriveActions`
2438
+
2439
+ ```typescript
2440
+ /** Generic action-derivation shape — concrete per-entity deriveActions implementations satisfy this. */
2441
+ export type DeriveActions<T extends Stateful, TAction> = (item: T) => TAction[]
2442
+ ```
2443
+
2444
+ ### `StatefulSchema`
2445
+
2446
+ ```typescript
2447
+ export const StatefulSchema = z.object({
2448
+ pipeline_key: z.string(),
2449
+ stage_key: z.string(),
2450
+ state_key: z.string(),
2451
+ activity_log: z.array(ActivityEventSchema)
2452
+ })
2453
+ ```
2454
+
2455
+ ### `StatefulStateDefinition`
2456
+
2457
+ ```typescript
2458
+ /** One state within a stage — minimal shape: key + display label. */
2459
+ export interface StatefulStateDefinition {
2460
+ /** Matches state_key values written by workflow steps. */
2461
+ stateKey: string
2462
+ label: string
2463
+ }
2464
+ ```
2465
+
2466
+ ### `StatefulStageDefinition`
2467
+
2468
+ ```typescript
2469
+ /** One stage within a pipeline — has a stage_key and an ordered list of valid states. */
2470
+ export interface StatefulStageDefinition {
2471
+ /** Matches stage_key values written by workflow steps. */
2472
+ stageKey: string
2473
+ label: string
2474
+ states: StatefulStateDefinition[]
2475
+ }
2476
+ ```
2477
+
2478
+ ### `StatefulPipelineDefinition`
2479
+
2480
+ ```typescript
2481
+ /**
2482
+ * Pipeline definition for a single entity participating in the Stateful trait.
2483
+ * Parallel to acq_deals' pipeline_key concept but structured for lead-gen entities.
2484
+ */
2485
+ export interface StatefulPipelineDefinition {
2486
+ /** Matches pipeline_key values in the database (e.g. 'lead-gen'). */
2487
+ pipelineKey: string
2488
+ label: string
2489
+ /** Entity this pipeline applies to (e.g. 'acq.list', 'acq.list-member', 'acq.list-company'). */
2490
+ entityKey: string
2491
+ stages: StatefulStageDefinition[]
2492
+ }
2493
+ ```
2494
+
2495
+ ### `ACQ_LIST_MEMBERS_LEAD_GEN_PIPELINE`
2496
+
2497
+ ```typescript
2498
+ /**
2499
+ * Lead-gen pipeline definition for acq_list_members (contacts).
2500
+ * Three stages matching the post-restructure sales subdomain tree.
2501
+ *
2502
+ * Note: members visit outreach and prospecting states depending on which
2503
+ * workflow last processed them. stage_key is set per-transition by the workflow.
2504
+ */
2505
+ export const ACQ_LIST_MEMBERS_LEAD_GEN_PIPELINE: StatefulPipelineDefinition = {
2506
+ pipelineKey: 'lead-gen',
2507
+ label: 'Lead Generation',
2508
+ entityKey: 'acq.list-member',
2509
+ stages: [
2510
+ {
2511
+ stageKey: 'outreach',
2512
+ label: 'Outreach',
2513
+ states: [
2514
+ PENDING_STATE,
2515
+ { stateKey: 'personalized', label: 'Personalized' },
2516
+ { stateKey: 'uploaded', label: 'Uploaded' },
2517
+ { stateKey: 'interested', label: 'Interested' }
2518
+ ]
2519
+ },
2520
+ {
2521
+ stageKey: 'prospecting',
2522
+ label: 'Prospecting',
2523
+ states: [
2524
+ PENDING_STATE,
2525
+ { stateKey: 'discovered', label: 'Discovered' },
2526
+ { stateKey: 'verified', label: 'Verified' }
2527
+ ]
2528
+ },
2529
+ {
2530
+ stageKey: 'qualification',
2531
+ label: 'Qualification',
2532
+ states: [PENDING_STATE]
2533
+ }
2534
+ ]
2535
+ }
2536
+ ```
2537
+
2538
+ ### `ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE`
2539
+
2540
+ ```typescript
2541
+ /**
2542
+ * Lead-gen pipeline definition for acq_list_companies.
2543
+ * Three stages matching the post-restructure sales subdomain tree.
2544
+ *
2545
+ * Note: companies visit prospecting and qualification states depending on which
2546
+ * workflow last processed them. stage_key is set per-transition by the workflow.
2547
+ */
2548
+ export const ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE: StatefulPipelineDefinition = {
2549
+ pipelineKey: 'lead-gen',
2550
+ label: 'Lead Generation',
2551
+ entityKey: 'acq.list-company',
2552
+ stages: [
2553
+ {
2554
+ stageKey: 'outreach',
2555
+ label: 'Outreach',
2556
+ states: [PENDING_STATE]
2557
+ },
2558
+ {
2559
+ stageKey: 'prospecting',
2560
+ label: 'Prospecting',
2561
+ states: [
2562
+ PENDING_STATE,
2563
+ { stateKey: 'populated', label: 'Populated' },
2564
+ { stateKey: 'extracted', label: 'Extracted' }
2565
+ ]
2566
+ },
2567
+ {
2568
+ stageKey: 'qualification',
2569
+ label: 'Qualification',
2570
+ states: [PENDING_STATE, { stateKey: 'qualified', label: 'Qualified' }]
2571
+ }
2572
+ ]
2573
+ }
2574
+ ```
2575
+
2576
+ ### `LEAD_GEN_PIPELINE_DEFINITIONS`
2577
+
2578
+ ```typescript
2579
+ /**
2580
+ * All lead-gen pipeline definitions indexed by entity key.
2581
+ * Use findPipeline() to locate a definition by pipeline_key within any of these arrays.
2582
+ */
2583
+ export const LEAD_GEN_PIPELINE_DEFINITIONS: Record<string, StatefulPipelineDefinition[]> = {
2584
+ 'acq.list-member': [ACQ_LIST_MEMBERS_LEAD_GEN_PIPELINE],
2585
+ 'acq.list-company': [ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE]
2586
+ }
2587
+ ```
2588
+
2589
+ ### `PaginationParams`
2590
+
2591
+ ```typescript
2592
+ export interface PaginationParams {
2593
+ limit: number
2594
+ offset: number
2595
+ }
2596
+ ```
2597
+
2598
+ ### `PaginatedResult`
2599
+
2600
+ ```typescript
2601
+ export interface PaginatedResult<T> {
2602
+ data: T[]
2603
+ total: number
2604
+ limit: number
2605
+ offset: number
2606
+ }
2607
+ ```
2608
+
2609
+ ### `CreateListParams`
2610
+
2611
+ ```typescript
2612
+ export interface CreateListParams {
2613
+ organizationId: string
2614
+ name: string
2615
+ description?: string
2616
+ type?: string
2617
+ batchIds?: string[]
2618
+ instantlyCampaignId?: string
2619
+ status?: ListStatus
2620
+ metadata?: Record<string, unknown>
2621
+ scrapingConfig?: ScrapingConfig
2622
+ icp?: IcpRubric
2623
+ pipelineConfig?: PipelineConfig
2624
+ }
2625
+ ```
2626
+
2627
+ ### `UpdateListParams`
2628
+
2629
+ ```typescript
2630
+ export interface UpdateListParams {
2631
+ name?: string
2632
+ description?: string
2633
+ batchIds?: string[]
2634
+ }
2635
+ ```
2636
+
2637
+ ### `CreateCompanyParams`
2638
+
2639
+ ```typescript
2640
+ export interface CreateCompanyParams {
2641
+ organizationId: string
2642
+ name: string
2643
+ domain?: string
2644
+ linkedinUrl?: string
2645
+ website?: string
2646
+ numEmployees?: number
2647
+ foundedYear?: number
2648
+ locationCity?: string
2649
+ locationState?: string
2650
+ category?: string
2651
+ source?: string
2652
+ batchId?: string
2653
+ verticalResearch?: string
2654
+ }
2655
+ ```
2656
+
2657
+ ### `UpdateCompanyParams`
2658
+
2659
+ ```typescript
2660
+ export interface UpdateCompanyParams {
2661
+ name?: string
2662
+ domain?: string
2663
+ linkedinUrl?: string
2664
+ website?: string
2665
+ numEmployees?: number
2666
+ foundedYear?: number
2667
+ locationCity?: string
2668
+ locationState?: string
2669
+ category?: string
2670
+ segment?: string
2671
+ pipelineStatus?: Record<string, unknown>
2672
+ enrichmentData?: Record<string, unknown>
2673
+ source?: string
2674
+ batchId?: string
2675
+ status?: 'active' | 'invalid'
2676
+ verticalResearch?: string | null
2677
+ /** Track A: flat qualification score column (null until a scoring rubric is defined) */
2678
+ qualificationScore?: number | null
2679
+ /** Track A: flat qualification signals jsonb — mirrors the former pipeline_status.qualification shape */
2680
+ qualificationSignals?: Record<string, unknown> | null
2681
+ /** Track A: key identifying the rubric used for qualification */
2682
+ qualificationRubricKey?: string | null
2683
+ }
2684
+ ```
2685
+
2686
+ ### `UpsertCompanyParams`
2687
+
2688
+ ```typescript
2689
+ export type UpsertCompanyParams = CreateCompanyParams
2690
+ ```
2691
+
2692
+ ### `CompanyFilters`
2693
+
2694
+ ```typescript
2695
+ export interface CompanyFilters {
2696
+ listId?: string // Filter to companies in a specific list (via acq_list_companies)
2697
+ search?: string
2698
+ domain?: string
2699
+ website?: string
2700
+ segment?: string
2701
+ category?: string
2702
+ pipelineStatus?: Record<string, unknown>
2703
+ /** Exclude companies whose pipeline_status contains this value (PostgREST NOT contains) */
2704
+ pipelineStatusNot?: Record<string, unknown>
2705
+ batchId?: string
2706
+ status?: 'active' | 'invalid'
2707
+ includeAll?: boolean
2708
+ excludeColumns?: Array<'enrichmentData' | 'pipelineStatus'>
2709
+ }
2710
+ ```
2711
+
2712
+ ### `CreateContactParams`
2713
+
2714
+ ```typescript
2715
+ export interface CreateContactParams {
2716
+ organizationId: string
2717
+ email: string
2718
+ companyId?: string
2719
+ firstName?: string
2720
+ lastName?: string
2721
+ linkedinUrl?: string
2722
+ title?: string
2723
+ source?: string
2724
+ sourceId?: string
2725
+ batchId?: string
2726
+ }
2727
+ ```
2728
+
2729
+ ### `UpdateContactParams`
2730
+
2731
+ ```typescript
2732
+ export interface UpdateContactParams {
2733
+ companyId?: string
2734
+ emailValid?: 'VALID' | 'INVALID' | 'RISKY' | 'UNKNOWN'
2735
+ firstName?: string
2736
+ lastName?: string
2737
+ linkedinUrl?: string
2738
+ title?: string
2739
+ headline?: string
2740
+ filterReason?: string
2741
+ openingLine?: string
2742
+ pipelineStatus?: Record<string, unknown>
2743
+ enrichmentData?: Record<string, unknown>
2744
+ status?: 'active' | 'invalid'
2745
+ }
2746
+ ```
2747
+
2748
+ ### `UpsertContactParams`
2749
+
2750
+ ```typescript
2751
+ export type UpsertContactParams = CreateContactParams
2752
+ ```
2753
+
2754
+ ### `ContactFilters`
2755
+
2756
+ ```typescript
2757
+ export interface ContactFilters {
2758
+ listId?: string // Filter to contacts in a specific list (via acq_list_members)
2759
+ search?: string
2760
+ openingLineIsNull?: boolean // Filter to contacts without personalization
2761
+ pipelineStatus?: Record<string, unknown>
2762
+ batchId?: string
2763
+ contactStatus?: 'active' | 'invalid' // Filter by contact status (soft-delete flag)
2764
+ }
2765
+ ```
2766
+
2767
+ ### `UpsertSocialPostParams`
2768
+
2769
+ ```typescript
2770
+ export interface UpsertSocialPostParams {
2771
+ organizationId: string
2772
+ platform: string
2773
+ platformPostId: string
2774
+ authorName: string
2775
+ authorUrl?: string | null
2776
+ postTitle: string
2777
+ postText: string
2778
+ postUrl: string
2779
+ engagementCount?: number
2780
+ commentsCount?: number
2781
+ postedAt: string
2782
+ metadata?: Record<string, unknown>
2783
+ relevanceScore?: number
2784
+ matchedKeywords?: string[]
2785
+ matchedQuery?: string | null
2786
+ initialDraft?: string | null
2787
+ finalResponse?: string | null
2788
+ sourceCategory?: string | null
2789
+ }
2790
+ ```
2791
+
2792
+ ### `UpsertSocialPostsParams`
2793
+
2794
+ ```typescript
2795
+ export interface UpsertSocialPostsParams {
2796
+ organizationId: string
2797
+ posts: Omit<UpsertSocialPostParams, 'organizationId'>[]
2798
+ }
2799
+ ```
2800
+
2801
+ ### `UpsertSocialPostsResult`
2802
+
2803
+ ```typescript
2804
+ export interface UpsertSocialPostsResult {
2805
+ inserted: number
2806
+ duplicatesSkipped: number
2807
+ }
2808
+ ```
2809
+
2810
+ ### `AddContactsToListParams`
2811
+
2812
+ ```typescript
2813
+ export interface AddContactsToListParams {
2814
+ organizationId: string
2815
+ listId: string
2816
+ contactIds: string[]
2817
+ }
2818
+ ```
2819
+
2820
+ ### `AddContactsToListResult`
2821
+
2822
+ ```typescript
2823
+ export interface AddContactsToListResult {
2824
+ added: number
2825
+ alreadyExisted: number
2826
+ }
2827
+ ```
2828
+
2829
+ ### `UpdateListConfigParams`
2830
+
2831
+ ```typescript
2832
+ export interface UpdateListConfigParams {
2833
+ organizationId: string
2834
+ listId: string
2835
+ scrapingConfig?: ScrapingConfig
2836
+ icp?: IcpRubric
2837
+ pipelineConfig?: PipelineConfig
2838
+ }
2839
+ ```
2840
+
2841
+ ### `UpdateCompanyStageParams`
2842
+
2843
+ ```typescript
2844
+ export interface UpdateCompanyStageParams {
2845
+ organizationId: string
2846
+ listId: string
2847
+ companyId: string
2848
+ stage: string
2849
+ status?: ProcessingStageStatus
2850
+ executionId?: string
2851
+ }
2852
+ ```
2853
+
2854
+ ### `UpdateContactStageParams`
2855
+
2856
+ ```typescript
2857
+ export interface UpdateContactStageParams {
2858
+ organizationId: string
2859
+ listId: string
2860
+ contactId: string
2861
+ stage: string
2862
+ status?: ProcessingStageStatus
2863
+ executionId?: string
2864
+ }
2865
+ ```
2866
+
2867
+ ### `AddCompaniesToListParams`
2868
+
2869
+ ```typescript
2870
+ export interface AddCompaniesToListParams {
2871
+ organizationId: string
2872
+ listId: string
2873
+ companyIds: string[]
2874
+ }
2875
+ ```
2876
+
2877
+ ### `AddCompaniesToListResult`
2878
+
2879
+ ```typescript
2880
+ export interface AddCompaniesToListResult {
2881
+ added: number
2882
+ alreadyExisted: number
2883
+ }
2884
+ ```
2885
+
2886
+ ### `RemoveCompaniesFromListParams`
2887
+
2888
+ ```typescript
2889
+ export interface RemoveCompaniesFromListParams {
2890
+ organizationId: string
2891
+ listId: string
2892
+ companyIds: string[]
2893
+ }
2894
+ ```
2895
+
2896
+ ### `RemoveCompaniesFromListResult`
2897
+
2898
+ ```typescript
2899
+ export interface RemoveCompaniesFromListResult {
2900
+ removed: number
2901
+ }
2902
+ ```
2903
+
2904
+ ### `RecordListExecutionParams`
2905
+
2906
+ ```typescript
2907
+ export interface RecordListExecutionParams {
2908
+ organizationId: string
2909
+ listId: string
2910
+ executionId: string
2911
+ configSnapshot?: Record<string, unknown>
2912
+ }
2913
+ ```
2914
+
2915
+ ### `ListExecutionSummary`
2916
+
2917
+ ```typescript
2918
+ export interface ListExecutionSummary {
2919
+ executionId: string
2920
+ resourceId: string
2921
+ status: string
2922
+ createdAt: string
2923
+ completedAt: string | null
2924
+ durationMs: number | null
2925
+ }
2926
+ ```
2927
+
2928
+ ### `BulkImportParams`
2929
+
2930
+ ```typescript
2931
+ export interface BulkImportParams {
2932
+ organizationId: string
2933
+ contacts: CreateContactParams[]
2934
+ listId?: string
2935
+ }
2936
+ ```
2937
+
2938
+ ### `BulkImportResult`
2939
+
2940
+ ```typescript
2941
+ export interface BulkImportResult {
2942
+ created: number
2943
+ updated: number
2944
+ errors: Array<{ email: string; error: string }>
2945
+ }
2946
+ ```
2947
+
2948
+ ### `BulkImportCompanyEntry`
2949
+
2950
+ ```typescript
2951
+ export interface BulkImportCompanyEntry {
2952
+ name: string
2953
+ domain: string
2954
+ website?: string
2955
+ locationCity?: string
2956
+ locationState?: string
2957
+ category?: string
2958
+ source?: string
2959
+ enrichmentData?: Record<string, unknown>
2960
+ pipelineStatus?: Record<string, unknown>
2961
+ }
2962
+ ```
2963
+
2964
+ ### `BulkImportCompaniesParams`
2965
+
2966
+ ```typescript
2967
+ export interface BulkImportCompaniesParams {
2968
+ organizationId: string
2969
+ batchId: string
2970
+ companies: BulkImportCompanyEntry[]
2971
+ }
2972
+ ```
2973
+
2974
+ ### `BulkImportCompaniesResult`
2975
+
2976
+ ```typescript
2977
+ export interface BulkImportCompaniesResult {
2978
+ created: number
2979
+ skipped: number
2980
+ errors: Array<{ companyName: string; error: string }>
2981
+ }
2982
+ ```
2983
+
2984
+ ### `LeadToolMap`
2985
+
2986
+ ```typescript
2987
+ export type LeadToolMap = {
2988
+ // List operations
2989
+ listLists: { params: Record<string, never>; result: AcqList[] }
2990
+ createList: { params: Omit<CreateListParams, 'organizationId'>; result: AcqList }
2991
+ updateList: { params: { id: string } & UpdateListParams; result: AcqList }
2992
+ deleteList: { params: { id: string }; result: void }
2993
+ addContactsToList: { params: Omit<AddContactsToListParams, 'organizationId'>; result: AddContactsToListResult }
2994
+ addCompaniesToList: { params: Omit<AddCompaniesToListParams, 'organizationId'>; result: AddCompaniesToListResult }
2995
+ updateCompanyStage: {
2996
+ params: Omit<UpdateCompanyStageParams, 'organizationId'>
2997
+ result: void
2998
+ }
2999
+ updateContactStage: {
3000
+ params: Omit<UpdateContactStageParams, 'organizationId'>
3001
+ result: void
3002
+ }
3003
+ // Company operations
3004
+ createCompany: { params: Omit<CreateCompanyParams, 'organizationId'>; result: AcqCompany }
3005
+ upsertCompany: { params: Omit<UpsertCompanyParams, 'organizationId'>; result: AcqCompany }
3006
+ updateCompany: { params: { id: string } & UpdateCompanyParams; result: AcqCompany }
3007
+ getCompany: { params: { id: string }; result: AcqCompany | null }
3008
+ listCompanies: { params: CompanyFilters; result: AcqCompany[] }
3009
+ deleteCompany: { params: { id: string }; result: void }
3010
+ // Contact operations
3011
+ createContact: { params: Omit<CreateContactParams, 'organizationId'>; result: AcqContact }
3012
+ upsertContact: { params: Omit<UpsertContactParams, 'organizationId'>; result: AcqContact }
3013
+ updateContact: { params: { id: string } & UpdateContactParams; result: AcqContact }
3014
+ getContact: { params: { id: string }; result: AcqContact | null }
3015
+ getContactByEmail: { params: { email: string }; result: AcqContact | null }
3016
+ listContacts: {
3017
+ params: ContactFilters & { limit?: number; offset?: number }
3018
+ result: PaginatedResult<AcqContact>
3019
+ }
3020
+ deleteContact: { params: { id: string }; result: void }
3021
+ bulkImportContacts: { params: Omit<BulkImportParams, 'organizationId'>; result: BulkImportResult }
3022
+ bulkImportCompanies: {
3023
+ params: Omit<BulkImportCompaniesParams, 'organizationId'>
3024
+ result: BulkImportCompaniesResult
3025
+ }
3026
+ deactivateContactsByCompany: {
3027
+ params: { companyId: string }
3028
+ result: { deactivated: number }
3029
+ }
3030
+ // Deal operations
3031
+ upsertDeal: { params: Omit<UpsertDealParams, 'organizationId'>; result: AcqDeal }
3032
+ getDealByEmail: { params: { email: string }; result: AcqDeal | null }
3033
+ getDealByEnvelopeId: { params: { envelopeId: string }; result: AcqDeal | null }
3034
+ updateDealEnvelopeId: { params: { dealId: string; envelopeId: string }; result: AcqDeal | null }
3035
+ getDealById: { params: Omit<GetDealByIdParams, 'organizationId'>; result: AcqDeal | null }
3036
+ getContactById: { params: Omit<GetContactByIdParams, 'organizationId'>; result: AcqContact | null }
3037
+ getCompanyById: { params: Omit<GetCompanyByIdParams, 'organizationId'>; result: AcqCompany | null }
3038
+ // Deal-sync operations
3039
+ updateDiscoveryData: { params: Omit<UpdateDiscoveryDataParams, 'organizationId'>; result: void }
3040
+ updateProposalData: { params: Omit<UpdateProposalDataParams, 'organizationId'>; result: void }
3041
+ markProposalSent: { params: Omit<MarkProposalSentParams, 'organizationId'>; result: void }
3042
+ markProposalReviewed: { params: Omit<MarkProposalReviewedParams, 'organizationId'>; result: void }
3043
+ updateCloseLostReason: { params: Omit<UpdateCloseLostReasonParams, 'organizationId'>; result: void }
3044
+ updateFees: { params: Omit<UpdateFeesParams, 'organizationId'>; result: void }
3045
+ transitionItem: { params: Omit<TransitionItemParams, 'organizationId'>; result: void }
3046
+ setContactNurture: { params: Omit<SetContactNurtureParams, 'organizationId'>; result: void }
3047
+ cancelSchedulesAndHitlByEmail: {
3048
+ params: Omit<CancelSchedulesAndHitlByEmailParams, 'organizationId'>
3049
+ result: { schedulesCancelled: number; hitlDeleted: number }
3050
+ }
3051
+ cancelHitlByDealId: { params: Omit<CancelHitlByDealIdParams, 'organizationId'>; result: { hitlDeleted: number } }
3052
+ clearDealFields: { params: Omit<ClearDealFieldsParams, 'organizationId'>; result: void }
3053
+ deleteDeal: { params: Omit<DeleteDealParams, 'organizationId'>; result: void }
3054
+ recordDealActivity: {
3055
+ params: Omit<RecordDealActivityParams, 'organizationId'>
3056
+ result: void
3057
+ }
3058
+ // Deal note operations
3059
+ createDealNote: {
3060
+ params: Omit<CreateDealNoteParams, 'organizationId'>
3061
+ result: AcqDealNote
3062
+ }
3063
+ listDealNotes: {
3064
+ params: Omit<ListDealNotesParams, 'organizationId'>
3065
+ result: AcqDealNote[]
3066
+ }
3067
+ // Deal task operations
3068
+ createDealTask: {
3069
+ params: Omit<CreateDealTaskParams, 'organizationId'>
3070
+ result: AcqDealTask
3071
+ }
3072
+ listDealTasks: {
3073
+ params: Omit<ListDealTasksParams, 'organizationId'>
3074
+ result: AcqDealTask[]
3075
+ }
3076
+ listDealTasksDue: {
3077
+ params: Omit<ListDealTasksDueParams, 'organizationId'>
3078
+ result: AcqDealTask[]
3079
+ }
3080
+ completeDealTask: {
3081
+ params: Omit<CompleteDealTaskParams, 'organizationId'>
3082
+ result: AcqDealTask
3083
+ }
3084
+ // Deal query & analytics operations
3085
+ listDeals: { params: DealFilters; result: AcqDeal[] }
3086
+ getDealPipelineAnalytics: { params: { recentLimit?: number }; result: DealPipelineAnalytics }
3087
+ // Enrichment data operations
3088
+ mergeEnrichmentData: {
3089
+ params: { id: string; table: 'acq_companies' | 'acq_contacts'; data: Record<string, unknown> }
3090
+ result: void
3091
+ }
3092
+ // Social monitoring operations
3093
+ upsertSocialPosts: {
3094
+ params: { posts: Omit<UpsertSocialPostParams, 'organizationId'>[] }
3095
+ result: UpsertSocialPostsResult
3096
+ }
3097
+ setDealStateKey: {
3098
+ params: {
3099
+ dealId: string
3100
+ stateKey: string
3101
+ }
3102
+ result: { ok: true }
3103
+ }
3104
+ // CRM workflow helpers
3105
+ transitionDeal: {
3106
+ params: {
3107
+ dealId: string
3108
+ toStage: string
3109
+ toState?: string
3110
+ }
3111
+ result: { deal: AcqDeal }
3112
+ }
3113
+ loadDeal: {
3114
+ params: { dealId: string }
3115
+ result: DealDetail | null
3116
+ }
3117
+ }
3118
+ ```
3119
+
3120
+ ### `ListToolMap`
3121
+
3122
+ ```typescript
3123
+ export type ListToolMap = {
3124
+ getConfig: {
3125
+ params: { listId: string }
3126
+ result: { scrapingConfig: ScrapingConfig; icp: IcpRubric; pipelineConfig: PipelineConfig }
3127
+ }
3128
+ recordExecution: {
3129
+ params: Omit<RecordListExecutionParams, 'organizationId'>
3130
+ result: void
3131
+ }
3132
+ updateCompanyStage: {
3133
+ params: Omit<UpdateCompanyStageParams, 'organizationId'>
3134
+ result: void
3135
+ }
3136
+ updateContactStage: {
3137
+ params: Omit<UpdateContactStageParams, 'organizationId'>
3138
+ result: void
3139
+ }
3140
+ }
3141
+ ```