@elevasis/core 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +108 -0
- package/dist/index.js +239 -27
- package/dist/knowledge/index.d.ts +55 -1
- package/dist/organization-model/index.d.ts +108 -0
- package/dist/organization-model/index.js +239 -27
- package/dist/test-utils/index.d.ts +54 -0
- package/dist/test-utils/index.js +238 -27
- package/package.json +1 -1
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +17 -5
- package/src/business/acquisition/api-schemas.test.ts +125 -14
- package/src/business/acquisition/api-schemas.ts +161 -11
- package/src/business/acquisition/build-templates.test.ts +28 -0
- package/src/business/acquisition/build-templates.ts +20 -8
- package/src/business/acquisition/derive-actions.test.ts +1 -1
- package/src/business/acquisition/types.ts +7 -2
- package/src/business/deals/api-schemas.ts +2 -2
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.test.ts +55 -0
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +107 -41
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.test.ts +48 -0
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.ts +99 -0
- package/src/execution/engine/tools/integration/server/adapters/apollo/index.ts +1 -0
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.test.ts +18 -0
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.ts +194 -0
- package/src/execution/engine/tools/integration/server/adapters/clickup/index.ts +7 -0
- package/src/integrations/credentials/api-schemas.ts +21 -2
- package/src/integrations/credentials/schemas.ts +200 -164
- package/src/organization-model/__tests__/graph.test.ts +108 -2
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +12 -12
- package/src/organization-model/__tests__/schema.test.ts +122 -0
- package/src/organization-model/__tests__/surface-projection.test.ts +174 -0
- package/src/organization-model/domains/prospecting.ts +273 -41
- package/src/organization-model/domains/sales.ts +32 -8
- package/src/organization-model/graph/build.ts +74 -0
- package/src/organization-model/graph/schema.ts +1 -0
- package/src/organization-model/graph/types.ts +1 -0
- package/src/organization-model/schema.ts +63 -0
- package/src/organization-model/surface-projection.ts +218 -0
- package/src/platform/constants/versions.ts +1 -1
- package/src/reference/_generated/contracts.md +17 -5
- package/src/server.ts +2 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import {
|
|
3
|
+
CRM_PIPELINE_DEFINITION,
|
|
3
4
|
DEFAULT_CRM_PRIORITY_RULE_CONFIG,
|
|
4
5
|
LEAD_GEN_PIPELINE_DEFINITIONS,
|
|
5
6
|
LEAD_GEN_STAGE_CATALOG
|
|
@@ -19,6 +20,9 @@ import {
|
|
|
19
20
|
CreateContactRequestSchema,
|
|
20
21
|
CreateDealNoteRequestSchema,
|
|
21
22
|
CreateDealTaskRequestSchema,
|
|
23
|
+
CrmStageKeySchema,
|
|
24
|
+
CrmStateKeySchema,
|
|
25
|
+
CrmTransitionItemRequestSchema,
|
|
22
26
|
CreateListRequestSchema,
|
|
23
27
|
DealDetailResponseSchema,
|
|
24
28
|
DealListItemSchema,
|
|
@@ -34,9 +38,11 @@ import {
|
|
|
34
38
|
ListDealsQuerySchema,
|
|
35
39
|
ListDealTasksDueQuerySchema,
|
|
36
40
|
ListMembersQuerySchema,
|
|
41
|
+
ListRecordsQuerySchema,
|
|
37
42
|
ListStatusSchema,
|
|
38
43
|
PipelineStageSchema,
|
|
39
44
|
ScrapingConfigSchema,
|
|
45
|
+
TransitionDealStateRequestSchema,
|
|
40
46
|
TransitionItemRequestSchema,
|
|
41
47
|
UpdateCompanyRequestSchema,
|
|
42
48
|
UpdateContactRequestSchema,
|
|
@@ -67,17 +73,31 @@ const PRIORITY = {
|
|
|
67
73
|
// ---------------------------------------------------------------------------
|
|
68
74
|
|
|
69
75
|
describe('DealStageSchema', () => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
const crmStageKeys = CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)
|
|
77
|
+
const crmStateKeys = CRM_PIPELINE_DEFINITION.stages.flatMap((stage) => stage.states.map((state) => state.stateKey))
|
|
78
|
+
|
|
79
|
+
it('derives CRM stage keys from CRM_PIPELINE_DEFINITION', () => {
|
|
80
|
+
expect(CrmStageKeySchema.options).toEqual(crmStageKeys)
|
|
81
|
+
expect(DealStageSchema.options).toEqual(crmStageKeys)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('derives CRM state keys from CRM_PIPELINE_DEFINITION', () => {
|
|
85
|
+
expect(CrmStateKeySchema.options).toEqual(crmStateKeys)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it.each(crmStageKeys)('accepts canonical stage "%s"', (stage) => {
|
|
89
|
+
expect(DealStageSchema.safeParse(stage).success).toBe(true)
|
|
90
|
+
})
|
|
76
91
|
|
|
77
92
|
it('rejects an unknown stage value', () => {
|
|
78
93
|
expect(DealStageSchema.safeParse('open').success).toBe(false)
|
|
79
94
|
expect(DealStageSchema.safeParse('').success).toBe(false)
|
|
80
95
|
})
|
|
96
|
+
|
|
97
|
+
it('rejects unknown CRM state values', () => {
|
|
98
|
+
expect(CrmStateKeySchema.safeParse('custom_state').success).toBe(false)
|
|
99
|
+
expect(CrmStateKeySchema.safeParse('').success).toBe(false)
|
|
100
|
+
})
|
|
81
101
|
})
|
|
82
102
|
|
|
83
103
|
// ---------------------------------------------------------------------------
|
|
@@ -86,7 +106,7 @@ describe('DealStageSchema', () => {
|
|
|
86
106
|
|
|
87
107
|
describe('TransitionItemRequestSchema', () => {
|
|
88
108
|
const valid = {
|
|
89
|
-
pipelineKey: '
|
|
109
|
+
pipelineKey: 'lead-gen',
|
|
90
110
|
stageKey: 'interested',
|
|
91
111
|
stateKey: null
|
|
92
112
|
}
|
|
@@ -122,11 +142,16 @@ describe('TransitionItemRequestSchema', () => {
|
|
|
122
142
|
})
|
|
123
143
|
|
|
124
144
|
it('accepts all canonical CRM deal stages', () => {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
for (const stageKey of CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)) {
|
|
146
|
+
expect(TransitionItemRequestSchema.safeParse({ pipelineKey: 'crm', stageKey, stateKey: null }).success).toBe(true)
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('accepts catalog-derived CRM state keys', () => {
|
|
151
|
+
for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
|
|
152
|
+
stage.states.map((state) => state.stateKey)
|
|
153
|
+
)) {
|
|
154
|
+
expect(TransitionItemRequestSchema.safeParse({ ...valid, stateKey }).success).toBe(true)
|
|
130
155
|
}
|
|
131
156
|
})
|
|
132
157
|
|
|
@@ -158,6 +183,11 @@ describe('TransitionItemRequestSchema', () => {
|
|
|
158
183
|
expect(result.success).toBe(false)
|
|
159
184
|
})
|
|
160
185
|
|
|
186
|
+
it('accepts unknown non-empty stage and state keys for generic substrate transitions', () => {
|
|
187
|
+
expect(TransitionItemRequestSchema.safeParse({ ...valid, stageKey: 'custom_stage' }).success).toBe(true)
|
|
188
|
+
expect(TransitionItemRequestSchema.safeParse({ ...valid, stateKey: 'custom_state' }).success).toBe(true)
|
|
189
|
+
})
|
|
190
|
+
|
|
161
191
|
it('rejects unknown top-level fields (strict mode)', () => {
|
|
162
192
|
const result = TransitionItemRequestSchema.safeParse({ ...valid, unknownField: 'x' })
|
|
163
193
|
expect(result.success).toBe(false)
|
|
@@ -176,6 +206,64 @@ describe('TransitionItemRequestSchema', () => {
|
|
|
176
206
|
})
|
|
177
207
|
})
|
|
178
208
|
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// CrmTransitionItemRequestSchema
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
describe('CrmTransitionItemRequestSchema', () => {
|
|
214
|
+
const valid = {
|
|
215
|
+
pipelineKey: 'crm',
|
|
216
|
+
stageKey: 'interested',
|
|
217
|
+
stateKey: null
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
it('accepts catalog-derived CRM stage and state keys', () => {
|
|
221
|
+
for (const stageKey of CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)) {
|
|
222
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stageKey }).success).toBe(true)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
|
|
226
|
+
stage.states.map((state) => state.stateKey)
|
|
227
|
+
)) {
|
|
228
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stateKey }).success).toBe(true)
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('rejects non-CRM pipeline keys and unknown CRM keys', () => {
|
|
233
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, pipelineKey: 'lead-gen' }).success).toBe(false)
|
|
234
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stageKey: 'unknown_stage' }).success).toBe(false)
|
|
235
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stateKey: 'unknown_state' }).success).toBe(false)
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// TransitionDealStateRequestSchema
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
|
|
243
|
+
describe('TransitionDealStateRequestSchema', () => {
|
|
244
|
+
it('accepts catalog-derived CRM state keys', () => {
|
|
245
|
+
for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
|
|
246
|
+
stage.states.map((state) => state.stateKey)
|
|
247
|
+
)) {
|
|
248
|
+
expect(TransitionDealStateRequestSchema.safeParse({ stateKey }).success).toBe(true)
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('rejects unknown state values', () => {
|
|
253
|
+
expect(TransitionDealStateRequestSchema.safeParse({ stateKey: 'unknown_state' }).success).toBe(false)
|
|
254
|
+
expect(TransitionDealStateRequestSchema.safeParse({ stateKey: '' }).success).toBe(false)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('preserves strict request schema behavior', () => {
|
|
258
|
+
expect(
|
|
259
|
+
TransitionDealStateRequestSchema.safeParse({
|
|
260
|
+
stateKey: CRM_PIPELINE_DEFINITION.stages[0]?.states[0]?.stateKey,
|
|
261
|
+
extra: 'x'
|
|
262
|
+
}).success
|
|
263
|
+
).toBe(false)
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
179
267
|
// ---------------------------------------------------------------------------
|
|
180
268
|
// ExecuteActionRequestSchema
|
|
181
269
|
// ---------------------------------------------------------------------------
|
|
@@ -698,7 +786,7 @@ describe('DealDetailResponseSchema (forward-compat)', () => {
|
|
|
698
786
|
organization_id: VALID_UUID,
|
|
699
787
|
contact_id: null,
|
|
700
788
|
contact_email: 'test@example.com',
|
|
701
|
-
pipeline_key: '
|
|
789
|
+
pipeline_key: 'crm',
|
|
702
790
|
stage_key: null,
|
|
703
791
|
state_key: null,
|
|
704
792
|
activity_log: [],
|
|
@@ -1514,6 +1602,30 @@ describe('ListMembersQuerySchema', () => {
|
|
|
1514
1602
|
})
|
|
1515
1603
|
})
|
|
1516
1604
|
|
|
1605
|
+
// ---------------------------------------------------------------------------
|
|
1606
|
+
// ListRecordsQuerySchema
|
|
1607
|
+
// ---------------------------------------------------------------------------
|
|
1608
|
+
|
|
1609
|
+
describe('ListRecordsQuerySchema', () => {
|
|
1610
|
+
it('accepts contact records for DTC decision-maker enrichment', () => {
|
|
1611
|
+
const result = ListRecordsQuerySchema.safeParse({
|
|
1612
|
+
entity: 'contact',
|
|
1613
|
+
stage: 'decision-makers-enriched'
|
|
1614
|
+
})
|
|
1615
|
+
|
|
1616
|
+
expect(result.success).toBe(true)
|
|
1617
|
+
})
|
|
1618
|
+
|
|
1619
|
+
it('keeps company records valid for qualified and uploaded stages', () => {
|
|
1620
|
+
expect(ListRecordsQuerySchema.safeParse({ entity: 'company', stage: 'qualified' }).success).toBe(true)
|
|
1621
|
+
expect(ListRecordsQuerySchema.safeParse({ entity: 'company', stage: 'uploaded' }).success).toBe(true)
|
|
1622
|
+
})
|
|
1623
|
+
|
|
1624
|
+
it('still rejects unrelated stage/entity combinations', () => {
|
|
1625
|
+
expect(ListRecordsQuerySchema.safeParse({ entity: 'contact', stage: 'qualified' }).success).toBe(false)
|
|
1626
|
+
})
|
|
1627
|
+
})
|
|
1628
|
+
|
|
1517
1629
|
// ---------------------------------------------------------------------------
|
|
1518
1630
|
// AcqListResponseSchema (forward-compat)
|
|
1519
1631
|
// ---------------------------------------------------------------------------
|
|
@@ -1585,5 +1697,4 @@ describe('AcqContactResponseSchema (forward-compat)', () => {
|
|
|
1585
1697
|
it('rejects an invalid emailValid value', () => {
|
|
1586
1698
|
expect(AcqContactResponseSchema.safeParse({ ...baseContact, emailValid: 'BAD' }).success).toBe(false)
|
|
1587
1699
|
})
|
|
1588
|
-
|
|
1589
1700
|
})
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { UuidSchema, NonEmptyStringSchema } from '../../platform/utils/validation'
|
|
3
|
-
import { LEAD_GEN_STAGE_CATALOG } from '../../organization-model/domains/sales'
|
|
4
|
-
import {
|
|
3
|
+
import { CRM_PIPELINE_DEFINITION, LEAD_GEN_STAGE_CATALOG } from '../../organization-model/domains/sales'
|
|
4
|
+
import {
|
|
5
|
+
CAPABILITY_REGISTRY,
|
|
6
|
+
CredentialRequirementSchema,
|
|
7
|
+
RecordColumnConfigSchema
|
|
8
|
+
} from '../../organization-model/domains/prospecting'
|
|
5
9
|
import { isProspectingBuildTemplateId } from './build-templates'
|
|
6
10
|
export { CrmPriorityBucketKeySchema, CrmPriorityBucketOverrideSchema, CrmPriorityOverrideSchema } from './crm-priority'
|
|
7
11
|
export type { CrmPriorityBucketOverride, CrmPriorityOverride, ResolvedCrmPriorityRuleConfig } from './crm-priority'
|
|
@@ -16,10 +20,19 @@ export const LeadGenStageKeySchema = z
|
|
|
16
20
|
|
|
17
21
|
export const LeadGenCapabilityKeySchema = z
|
|
18
22
|
.string()
|
|
19
|
-
.refine((value) =>
|
|
23
|
+
.refine((value) => CAPABILITY_REGISTRY.some((c) => c.id === value), {
|
|
20
24
|
message: 'capabilityKey must match CAPABILITY_REGISTRY'
|
|
21
25
|
})
|
|
22
26
|
|
|
27
|
+
const crmStageKeys = CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey) as [string, ...string[]]
|
|
28
|
+
const crmStateKeys = CRM_PIPELINE_DEFINITION.stages.flatMap((stage) => stage.states.map((state) => state.stateKey)) as [
|
|
29
|
+
string,
|
|
30
|
+
...string[]
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
export const CrmStageKeySchema = z.enum(crmStageKeys)
|
|
34
|
+
export const CrmStateKeySchema = z.enum(crmStateKeys)
|
|
35
|
+
|
|
23
36
|
export const ProcessingStateEntrySchema = z
|
|
24
37
|
.object({
|
|
25
38
|
status: ProcessingStageStatusSchema,
|
|
@@ -47,7 +60,7 @@ export const ContactProcessingStateSchema = ProcessingStateSchema
|
|
|
47
60
|
// Enum literals (must match DB CHECK constraints exactly)
|
|
48
61
|
// ---------------------------------------------------------------------------
|
|
49
62
|
|
|
50
|
-
export const DealStageSchema =
|
|
63
|
+
export const DealStageSchema = CrmStageKeySchema
|
|
51
64
|
|
|
52
65
|
export const AcqDealTaskKindSchema = z.enum(['call', 'email', 'meeting', 'other'])
|
|
53
66
|
|
|
@@ -115,7 +128,17 @@ export const TransitionItemRequestSchema = z
|
|
|
115
128
|
.object({
|
|
116
129
|
pipelineKey: z.string().min(1),
|
|
117
130
|
stageKey: z.string().min(1),
|
|
118
|
-
stateKey: z.string().nullable().optional(),
|
|
131
|
+
stateKey: z.string().min(1).nullable().optional(),
|
|
132
|
+
reason: z.string().optional(),
|
|
133
|
+
expectedUpdatedAt: z.string().datetime().optional()
|
|
134
|
+
})
|
|
135
|
+
.strict()
|
|
136
|
+
|
|
137
|
+
export const CrmTransitionItemRequestSchema = z
|
|
138
|
+
.object({
|
|
139
|
+
pipelineKey: z.literal(CRM_PIPELINE_DEFINITION.pipelineKey),
|
|
140
|
+
stageKey: CrmStageKeySchema,
|
|
141
|
+
stateKey: CrmStateKeySchema.nullable().optional(),
|
|
119
142
|
reason: z.string().optional(),
|
|
120
143
|
expectedUpdatedAt: z.string().datetime().optional()
|
|
121
144
|
})
|
|
@@ -123,7 +146,7 @@ export const TransitionItemRequestSchema = z
|
|
|
123
146
|
|
|
124
147
|
export const TransitionDealStateRequestSchema = z
|
|
125
148
|
.object({
|
|
126
|
-
stateKey:
|
|
149
|
+
stateKey: CrmStateKeySchema,
|
|
127
150
|
reason: z.string().optional(),
|
|
128
151
|
expectedUpdatedAt: z.string().datetime().optional()
|
|
129
152
|
})
|
|
@@ -331,6 +354,11 @@ export const DealTaskListResponseSchema = z.array(DealTaskResponseSchema)
|
|
|
331
354
|
// ---------------------------------------------------------------------------
|
|
332
355
|
|
|
333
356
|
export const DealSchemas = {
|
|
357
|
+
// Primitives
|
|
358
|
+
CrmStageKey: CrmStageKeySchema,
|
|
359
|
+
CrmStateKey: CrmStateKeySchema,
|
|
360
|
+
DealStage: DealStageSchema,
|
|
361
|
+
|
|
334
362
|
// Params
|
|
335
363
|
DealIdParams: DealIdParamsSchema,
|
|
336
364
|
DealTaskIdParams: DealTaskIdParamsSchema,
|
|
@@ -343,7 +371,7 @@ export const DealSchemas = {
|
|
|
343
371
|
// Request bodies
|
|
344
372
|
CreateDealNoteRequest: CreateDealNoteRequestSchema,
|
|
345
373
|
CreateDealTaskRequest: CreateDealTaskRequestSchema,
|
|
346
|
-
TransitionItemRequest:
|
|
374
|
+
TransitionItemRequest: CrmTransitionItemRequestSchema,
|
|
347
375
|
TransitionDealStateRequest: TransitionDealStateRequestSchema,
|
|
348
376
|
ExecuteActionParams: ExecuteActionParamsSchema,
|
|
349
377
|
ExecuteActionRequest: ExecuteActionRequestSchema,
|
|
@@ -366,6 +394,8 @@ export const DealSchemas = {
|
|
|
366
394
|
// ---------------------------------------------------------------------------
|
|
367
395
|
|
|
368
396
|
export type DealStage = z.infer<typeof DealStageSchema>
|
|
397
|
+
export type CrmStageKey = z.infer<typeof CrmStageKeySchema>
|
|
398
|
+
export type CrmStateKey = z.infer<typeof CrmStateKeySchema>
|
|
369
399
|
export type AcqDealTaskKind = z.infer<typeof AcqDealTaskKindSchema>
|
|
370
400
|
export type DealIdParams = z.infer<typeof DealIdParamsSchema>
|
|
371
401
|
export type DealTaskIdParams = z.infer<typeof DealTaskIdParamsSchema>
|
|
@@ -375,6 +405,7 @@ export type ListDealTasksDueQuery = z.infer<typeof ListDealTasksDueQuerySchema>
|
|
|
375
405
|
export type CreateDealNoteRequest = z.infer<typeof CreateDealNoteRequestSchema>
|
|
376
406
|
export type CreateDealTaskRequest = z.infer<typeof CreateDealTaskRequestSchema>
|
|
377
407
|
export type TransitionItemRequest = z.infer<typeof TransitionItemRequestSchema>
|
|
408
|
+
export type CrmTransitionItemRequest = z.infer<typeof CrmTransitionItemRequestSchema>
|
|
378
409
|
export type TransitionDealStateRequest = z.infer<typeof TransitionDealStateRequestSchema>
|
|
379
410
|
export type ExecuteActionParams = z.infer<typeof ExecuteActionParamsSchema>
|
|
380
411
|
export type ExecuteActionRequest = z.infer<typeof ExecuteActionRequestSchema>
|
|
@@ -469,11 +500,21 @@ export const BuildPlanSnapshotStepSchema = z
|
|
|
469
500
|
primaryEntity: z.enum(['company', 'contact']),
|
|
470
501
|
outputs: z.array(z.enum(['company', 'contact', 'export'])).min(1),
|
|
471
502
|
stageKey: LeadGenStageKeySchema,
|
|
503
|
+
recordEntity: z.enum(['company', 'contact']).optional(),
|
|
504
|
+
recordsStageKey: LeadGenStageKeySchema.optional(),
|
|
505
|
+
recordSourceStageKey: LeadGenStageKeySchema.optional(),
|
|
472
506
|
dependsOn: z.array(z.string().trim().min(1).max(100)).optional(),
|
|
473
507
|
dependencyMode: z.literal('per-record-eligibility'),
|
|
474
508
|
capabilityKey: LeadGenCapabilityKeySchema,
|
|
475
509
|
defaultBatchSize: z.number().int().positive(),
|
|
476
|
-
maxBatchSize: z.number().int().positive()
|
|
510
|
+
maxBatchSize: z.number().int().positive(),
|
|
511
|
+
recordColumns: z
|
|
512
|
+
.object({
|
|
513
|
+
company: z.array(RecordColumnConfigSchema).optional(),
|
|
514
|
+
contact: z.array(RecordColumnConfigSchema).optional()
|
|
515
|
+
})
|
|
516
|
+
.optional(),
|
|
517
|
+
credentialRequirements: z.array(CredentialRequirementSchema).optional()
|
|
477
518
|
})
|
|
478
519
|
.refine((step) => step.defaultBatchSize <= step.maxBatchSize, {
|
|
479
520
|
message: 'defaultBatchSize must be less than or equal to maxBatchSize',
|
|
@@ -1036,6 +1077,33 @@ export const ListMembersQuerySchema = z
|
|
|
1036
1077
|
})
|
|
1037
1078
|
.strict()
|
|
1038
1079
|
|
|
1080
|
+
export const ListRecordEntitySchema = z.enum(['company', 'contact'])
|
|
1081
|
+
|
|
1082
|
+
export const ListRecordsQuerySchema = z
|
|
1083
|
+
.object({
|
|
1084
|
+
entity: ListRecordEntitySchema,
|
|
1085
|
+
stage: LeadGenStageKeySchema.optional(),
|
|
1086
|
+
limit: z.coerce.number().int().min(1).max(500).default(50),
|
|
1087
|
+
offset: z.coerce.number().int().min(0).default(0)
|
|
1088
|
+
})
|
|
1089
|
+
.strict()
|
|
1090
|
+
.superRefine((query, ctx) => {
|
|
1091
|
+
if (!query.stage) return
|
|
1092
|
+
|
|
1093
|
+
const stage = LEAD_GEN_STAGE_CATALOG[query.stage]
|
|
1094
|
+
const validEntity =
|
|
1095
|
+
stage?.entity === query.entity ||
|
|
1096
|
+
stage?.additionalEntities?.includes(query.entity) ||
|
|
1097
|
+
stage?.recordEntity === query.entity
|
|
1098
|
+
if (!validEntity) {
|
|
1099
|
+
ctx.addIssue({
|
|
1100
|
+
code: z.ZodIssueCode.custom,
|
|
1101
|
+
message: `stage "${query.stage}" is not valid for ${query.entity} records`,
|
|
1102
|
+
path: ['stage']
|
|
1103
|
+
})
|
|
1104
|
+
}
|
|
1105
|
+
})
|
|
1106
|
+
|
|
1039
1107
|
export const MemberIdParamsSchema = z.object({
|
|
1040
1108
|
memberId: UuidSchema
|
|
1041
1109
|
})
|
|
@@ -1068,6 +1136,77 @@ export const AcqListMembersResponseSchema = z.object({
|
|
|
1068
1136
|
members: z.array(AcqListMemberResponseSchema)
|
|
1069
1137
|
})
|
|
1070
1138
|
|
|
1139
|
+
export const AcqListRecordCompanySummarySchema = z.object({
|
|
1140
|
+
id: z.string(),
|
|
1141
|
+
name: z.string(),
|
|
1142
|
+
domain: z.string().nullable(),
|
|
1143
|
+
website: z.string().nullable(),
|
|
1144
|
+
linkedinUrl: z.string().nullable(),
|
|
1145
|
+
numEmployees: z.number().nullable(),
|
|
1146
|
+
foundedYear: z.number().nullable(),
|
|
1147
|
+
locationCity: z.string().nullable(),
|
|
1148
|
+
locationState: z.string().nullable(),
|
|
1149
|
+
category: z.string().nullable(),
|
|
1150
|
+
segment: z.string().nullable(),
|
|
1151
|
+
status: AcqCompanyStatusSchema,
|
|
1152
|
+
qualificationScore: z.number().nullable(),
|
|
1153
|
+
qualificationSignals: z.record(z.string(), z.unknown()).nullable(),
|
|
1154
|
+
qualificationRubricKey: z.string().nullable()
|
|
1155
|
+
})
|
|
1156
|
+
|
|
1157
|
+
export const AcqListRecordContactSummarySchema = z.object({
|
|
1158
|
+
id: z.string(),
|
|
1159
|
+
email: z.string(),
|
|
1160
|
+
firstName: z.string().nullable(),
|
|
1161
|
+
lastName: z.string().nullable(),
|
|
1162
|
+
title: z.string().nullable(),
|
|
1163
|
+
headline: z.string().nullable(),
|
|
1164
|
+
linkedinUrl: z.string().nullable(),
|
|
1165
|
+
companyId: z.string().nullable(),
|
|
1166
|
+
status: AcqContactStatusSchema,
|
|
1167
|
+
qualificationScore: z.number().nullable(),
|
|
1168
|
+
qualificationSignals: z.record(z.string(), z.unknown()).nullable(),
|
|
1169
|
+
qualificationRubricKey: z.string().nullable()
|
|
1170
|
+
})
|
|
1171
|
+
|
|
1172
|
+
const ListRecordBaseSchema = z.object({
|
|
1173
|
+
id: z.string(),
|
|
1174
|
+
listId: z.string(),
|
|
1175
|
+
pipelineKey: z.string(),
|
|
1176
|
+
stageKey: z.string(),
|
|
1177
|
+
stateKey: z.string(),
|
|
1178
|
+
activityLog: z.unknown(),
|
|
1179
|
+
addedAt: z.string(),
|
|
1180
|
+
addedBy: z.string().nullable(),
|
|
1181
|
+
sourceExecutionId: z.string().nullable(),
|
|
1182
|
+
processingState: z.record(z.string(), z.unknown()).nullable(),
|
|
1183
|
+
enrichmentData: z.record(z.string(), z.unknown()).nullable()
|
|
1184
|
+
})
|
|
1185
|
+
|
|
1186
|
+
export const AcqListCompanyRecordRowSchema = ListRecordBaseSchema.extend({
|
|
1187
|
+
entity: z.literal('company'),
|
|
1188
|
+
companyId: z.string(),
|
|
1189
|
+
company: AcqListRecordCompanySummarySchema.nullable()
|
|
1190
|
+
})
|
|
1191
|
+
|
|
1192
|
+
export const AcqListContactRecordRowSchema = ListRecordBaseSchema.extend({
|
|
1193
|
+
entity: z.literal('contact'),
|
|
1194
|
+
contactId: z.string(),
|
|
1195
|
+
contact: AcqListRecordContactSummarySchema.nullable()
|
|
1196
|
+
})
|
|
1197
|
+
|
|
1198
|
+
export const ListRecordRowSchema = z.discriminatedUnion('entity', [
|
|
1199
|
+
AcqListCompanyRecordRowSchema,
|
|
1200
|
+
AcqListContactRecordRowSchema
|
|
1201
|
+
])
|
|
1202
|
+
|
|
1203
|
+
export const ListRecordsResponseSchema = z.object({
|
|
1204
|
+
data: z.array(ListRecordRowSchema),
|
|
1205
|
+
total: z.number().int().min(0),
|
|
1206
|
+
limit: z.number().int().min(1),
|
|
1207
|
+
offset: z.number().int().min(0)
|
|
1208
|
+
})
|
|
1209
|
+
|
|
1071
1210
|
// ---------------------------------------------------------------------------
|
|
1072
1211
|
// Track B: List Companies API Schemas
|
|
1073
1212
|
// ---------------------------------------------------------------------------
|
|
@@ -1090,8 +1229,8 @@ export const AcqListCompanyResponseSchema = z.object({
|
|
|
1090
1229
|
})
|
|
1091
1230
|
|
|
1092
1231
|
// ---------------------------------------------------------------------------
|
|
1093
|
-
// Track B: Transition
|
|
1094
|
-
//
|
|
1232
|
+
// Track B: Transition request for list, list-member, and list-company substrate routes.
|
|
1233
|
+
// CRM deals use DealSchemas.TransitionItemRequest, which is catalog-backed.
|
|
1095
1234
|
// ---------------------------------------------------------------------------
|
|
1096
1235
|
|
|
1097
1236
|
export const AcqCompanySchemas = {
|
|
@@ -1189,15 +1328,20 @@ export const AcqSubstrateSchemas = {
|
|
|
1189
1328
|
|
|
1190
1329
|
// List members
|
|
1191
1330
|
ListMembersQuery: ListMembersQuerySchema,
|
|
1331
|
+
ListRecordsQuery: ListRecordsQuerySchema,
|
|
1192
1332
|
MemberIdParams: MemberIdParamsSchema,
|
|
1193
1333
|
AcqListMemberResponse: AcqListMemberResponseSchema,
|
|
1194
1334
|
AcqListMembersResponse: AcqListMembersResponseSchema,
|
|
1335
|
+
AcqListCompanyRecordRow: AcqListCompanyRecordRowSchema,
|
|
1336
|
+
AcqListContactRecordRow: AcqListContactRecordRowSchema,
|
|
1337
|
+
ListRecordRow: ListRecordRowSchema,
|
|
1338
|
+
ListRecordsResponse: ListRecordsResponseSchema,
|
|
1195
1339
|
|
|
1196
1340
|
// List companies
|
|
1197
1341
|
ListCompanyIdParams: ListCompanyIdParamsSchema,
|
|
1198
1342
|
AcqListCompanyResponse: AcqListCompanyResponseSchema,
|
|
1199
1343
|
|
|
1200
|
-
// Transition (
|
|
1344
|
+
// Transition (generic stateful substrate)
|
|
1201
1345
|
TransitionItemRequest: TransitionItemRequestSchema
|
|
1202
1346
|
}
|
|
1203
1347
|
|
|
@@ -1215,10 +1359,16 @@ export type CreateArtifactRequest = z.infer<typeof CreateArtifactRequestSchema>
|
|
|
1215
1359
|
export type AcqArtifactResponse = z.infer<typeof AcqArtifactResponseSchema>
|
|
1216
1360
|
export type AcqArtifactListResponse = z.infer<typeof AcqArtifactListResponseSchema>
|
|
1217
1361
|
export type ListMembersQuery = z.infer<typeof ListMembersQuerySchema>
|
|
1362
|
+
export type ListRecordEntity = z.infer<typeof ListRecordEntitySchema>
|
|
1363
|
+
export type ListRecordsQuery = z.infer<typeof ListRecordsQuerySchema>
|
|
1218
1364
|
export type MemberIdParams = z.infer<typeof MemberIdParamsSchema>
|
|
1219
1365
|
export type AcqListMemberContactSummary = z.infer<typeof AcqListMemberContactSummarySchema>
|
|
1220
1366
|
export type AcqListMemberResponse = z.infer<typeof AcqListMemberResponseSchema>
|
|
1221
1367
|
export type AcqListMembersResponse = z.infer<typeof AcqListMembersResponseSchema>
|
|
1368
|
+
export type AcqListCompanyRecordRow = z.infer<typeof AcqListCompanyRecordRowSchema>
|
|
1369
|
+
export type AcqListContactRecordRow = z.infer<typeof AcqListContactRecordRowSchema>
|
|
1370
|
+
export type ListRecordRow = z.infer<typeof ListRecordRowSchema>
|
|
1371
|
+
export type ListRecordsResponse = z.infer<typeof ListRecordsResponseSchema>
|
|
1222
1372
|
export type ListCompanyIdParams = z.infer<typeof ListCompanyIdParamsSchema>
|
|
1223
1373
|
export type AcqListCompanyResponse = z.infer<typeof AcqListCompanyResponseSchema>
|
|
1224
1374
|
|
|
@@ -180,12 +180,40 @@ describe('createBuildPlanSnapshotFromTemplateId — "dtc-subscription-apollo-cli
|
|
|
180
180
|
expect(first?.outputs).toContain('contact')
|
|
181
181
|
})
|
|
182
182
|
|
|
183
|
+
it('preserves Apollo credential requirements for source import and decision-maker enrichment', () => {
|
|
184
|
+
const importApolloSearch = snapshot?.steps.find((step) => step.id === 'import-apollo-search')
|
|
185
|
+
const enrichDecisionMakers = snapshot?.steps.find((step) => step.id === 'enrich-decision-makers')
|
|
186
|
+
|
|
187
|
+
const expectedRequirement = {
|
|
188
|
+
key: 'apollo',
|
|
189
|
+
provider: 'apollo',
|
|
190
|
+
credentialType: 'api-key-secret',
|
|
191
|
+
label: 'Apollo API key',
|
|
192
|
+
required: true,
|
|
193
|
+
selectionMode: 'single',
|
|
194
|
+
inputPath: 'credential'
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
expect(importApolloSearch?.credentialRequirements).toEqual([expectedRequirement])
|
|
198
|
+
expect(enrichDecisionMakers?.credentialRequirements).toEqual([expectedRequirement])
|
|
199
|
+
})
|
|
200
|
+
|
|
183
201
|
it('the final step (review-and-export) outputs export', () => {
|
|
184
202
|
const last = snapshot?.steps[snapshot.steps.length - 1]
|
|
185
203
|
expect(last?.id).toBe('review-and-export')
|
|
186
204
|
expect(last?.outputs).toContain('export')
|
|
187
205
|
})
|
|
188
206
|
|
|
207
|
+
it('preserves contact records metadata for the company-owned decision-maker step', () => {
|
|
208
|
+
const decisionMakers = snapshot?.steps.find((step) => step.id === 'enrich-decision-makers')
|
|
209
|
+
expect(decisionMakers).toMatchObject({
|
|
210
|
+
primaryEntity: 'company',
|
|
211
|
+
recordEntity: 'contact',
|
|
212
|
+
stageKey: 'decision-makers-enriched'
|
|
213
|
+
})
|
|
214
|
+
expect(decisionMakers?.recordColumns?.contact?.length).toBeGreaterThan(0)
|
|
215
|
+
})
|
|
216
|
+
|
|
189
217
|
it('steps with descriptions in the catalog include description in the snapshot', () => {
|
|
190
218
|
const withDesc = snapshot?.steps.filter((step) => step.description !== undefined) ?? []
|
|
191
219
|
expect(withDesc.length).toBeGreaterThan(0)
|
|
@@ -29,16 +29,28 @@ export function createBuildPlanSnapshotFromTemplateId(templateId: string): Build
|
|
|
29
29
|
primaryEntity: step.primaryEntity,
|
|
30
30
|
outputs: [...step.outputs],
|
|
31
31
|
stageKey: step.stageKey,
|
|
32
|
+
recordsStageKey: step.recordsStageKey ?? step.stageKey,
|
|
33
|
+
recordSourceStageKey: step.recordSourceStageKey ?? step.recordsStageKey ?? step.stageKey,
|
|
32
34
|
dependencyMode: step.dependencyMode,
|
|
33
35
|
capabilityKey: step.capabilityKey,
|
|
34
36
|
defaultBatchSize: step.defaultBatchSize,
|
|
35
37
|
maxBatchSize: step.maxBatchSize
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (step.description) snapshotStep.description = step.description
|
|
39
|
-
if (step.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (step.description) snapshotStep.description = step.description
|
|
41
|
+
if (step.recordEntity) snapshotStep.recordEntity = step.recordEntity
|
|
42
|
+
if (step.dependsOn?.length) snapshotStep.dependsOn = [...step.dependsOn]
|
|
43
|
+
if (step.credentialRequirements?.length) {
|
|
44
|
+
snapshotStep.credentialRequirements = step.credentialRequirements.map((requirement) => ({ ...requirement }))
|
|
45
|
+
}
|
|
46
|
+
if (step.recordColumns) {
|
|
47
|
+
snapshotStep.recordColumns = {
|
|
48
|
+
...(step.recordColumns.company ? { company: step.recordColumns.company.map((column) => ({ ...column })) } : {}),
|
|
49
|
+
...(step.recordColumns.contact ? { contact: step.recordColumns.contact.map((column) => ({ ...column })) } : {})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return snapshotStep
|
|
54
|
+
})
|
|
55
|
+
}
|
|
44
56
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Database } from '../../supabase/database.types'
|
|
2
|
-
import type {
|
|
2
|
+
import type { Capability, CredentialRequirement, RecordColumnConfig } from '../../organization-model/domains/prospecting'
|
|
3
3
|
import type { LEAD_GEN_STAGE_CATALOG } from '../../organization-model/domains/sales'
|
|
4
4
|
import type { PipelineStage, ProcessingStageStatus } from './api-schemas'
|
|
5
5
|
|
|
@@ -52,7 +52,7 @@ export interface WebPost {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export type LeadGenStageKey = (typeof LEAD_GEN_STAGE_CATALOG)[keyof typeof LEAD_GEN_STAGE_CATALOG]['key']
|
|
55
|
-
export type LeadGenCapabilityKey =
|
|
55
|
+
export type LeadGenCapabilityKey = Capability['id']
|
|
56
56
|
|
|
57
57
|
export interface ProcessingStateEntry {
|
|
58
58
|
status: ProcessingStageStatus
|
|
@@ -167,11 +167,16 @@ export interface BuildPlanSnapshotStep {
|
|
|
167
167
|
primaryEntity: BuildPlanSnapshotPrimaryEntity
|
|
168
168
|
outputs: BuildPlanSnapshotOutput[]
|
|
169
169
|
stageKey: string
|
|
170
|
+
recordEntity?: BuildPlanSnapshotPrimaryEntity
|
|
171
|
+
recordsStageKey?: string
|
|
172
|
+
recordSourceStageKey?: string
|
|
170
173
|
dependsOn?: string[]
|
|
171
174
|
dependencyMode: BuildPlanSnapshotDependencyMode
|
|
172
175
|
capabilityKey: string
|
|
173
176
|
defaultBatchSize: number
|
|
174
177
|
maxBatchSize: number
|
|
178
|
+
recordColumns?: Partial<Record<BuildPlanSnapshotPrimaryEntity, RecordColumnConfig[]>>
|
|
179
|
+
credentialRequirements?: CredentialRequirement[]
|
|
175
180
|
}
|
|
176
181
|
|
|
177
182
|
export interface BuildPlanSnapshot {
|
|
@@ -27,7 +27,7 @@ export {
|
|
|
27
27
|
// Request body schemas
|
|
28
28
|
CreateDealNoteRequestSchema,
|
|
29
29
|
CreateDealTaskRequestSchema,
|
|
30
|
-
TransitionItemRequestSchema,
|
|
30
|
+
CrmTransitionItemRequestSchema as TransitionItemRequestSchema,
|
|
31
31
|
TransitionDealStateRequestSchema,
|
|
32
32
|
ExecuteActionParamsSchema,
|
|
33
33
|
ExecuteActionRequestSchema,
|
|
@@ -61,7 +61,7 @@ export {
|
|
|
61
61
|
type ListDealTasksDueQuery,
|
|
62
62
|
type CreateDealNoteRequest,
|
|
63
63
|
type CreateDealTaskRequest,
|
|
64
|
-
type TransitionItemRequest,
|
|
64
|
+
type CrmTransitionItemRequest as TransitionItemRequest,
|
|
65
65
|
type TransitionDealStateRequest,
|
|
66
66
|
type ExecuteActionParams,
|
|
67
67
|
type ExecuteActionRequest,
|