@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,164 +1,200 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Credential field definition
|
|
5
|
-
*/
|
|
6
|
-
export interface CredentialField {
|
|
7
|
-
key: string
|
|
8
|
-
label: string
|
|
9
|
-
type: 'password' | 'text'
|
|
10
|
-
required: boolean
|
|
11
|
-
placeholder?: string
|
|
12
|
-
description?: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Credential schema definition
|
|
17
|
-
*/
|
|
18
|
-
export interface CredentialSchema {
|
|
19
|
-
type: string
|
|
20
|
-
label: string
|
|
21
|
-
description: string
|
|
22
|
-
fields?: CredentialField[]
|
|
23
|
-
docsUrl?: string
|
|
24
|
-
nameSuggestions: string[]
|
|
25
|
-
oauthProvider?: string // For OAuth types
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Zod schemas for runtime validation
|
|
30
|
-
*/
|
|
31
|
-
const CredentialFieldSchema = z.object({
|
|
32
|
-
key: z.string(),
|
|
33
|
-
label: z.string(),
|
|
34
|
-
type: z.enum(['password', 'text']),
|
|
35
|
-
required: z.boolean(),
|
|
36
|
-
placeholder: z.string().optional(),
|
|
37
|
-
description: z.string().optional()
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
const CredentialSchemaZod = z.object({
|
|
41
|
-
type: z.string(),
|
|
42
|
-
label: z.string(),
|
|
43
|
-
description: z.string(),
|
|
44
|
-
fields: z.array(CredentialFieldSchema).optional(),
|
|
45
|
-
docsUrl: z.string().url().optional(),
|
|
46
|
-
nameSuggestions: z.array(z.string()),
|
|
47
|
-
oauthProvider: z.string().optional()
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Credential Schema Registry
|
|
52
|
-
*
|
|
53
|
-
* Add new integration = add schema object here (no modal changes needed)
|
|
54
|
-
*
|
|
55
|
-
* Schema types:
|
|
56
|
-
* - oauth: OAuth-based authentication (handled via OAuth flow)
|
|
57
|
-
* - single_field: Single API key field (generic)
|
|
58
|
-
*/
|
|
59
|
-
export const CREDENTIAL_SCHEMAS: Record<string, CredentialSchema> = {
|
|
60
|
-
'google-sheets': {
|
|
61
|
-
type: 'oauth',
|
|
62
|
-
label: 'Google Sheets',
|
|
63
|
-
description: 'Google Sheets OAuth integration',
|
|
64
|
-
oauthProvider: 'google-sheets',
|
|
65
|
-
nameSuggestions: ['google-sheets-prod', 'google-sheets-dev', 'sheets-workspace']
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
dropbox: {
|
|
69
|
-
type: 'oauth',
|
|
70
|
-
label: 'Dropbox',
|
|
71
|
-
description: 'Dropbox OAuth integration',
|
|
72
|
-
oauthProvider: 'dropbox',
|
|
73
|
-
nameSuggestions: ['dropbox-prod', 'dropbox-dev', 'elevasis-dropbox']
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
oauth: {
|
|
77
|
-
type: 'oauth',
|
|
78
|
-
label: 'OAuth',
|
|
79
|
-
description: 'Generic OAuth credential',
|
|
80
|
-
nameSuggestions: []
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
'api-key': {
|
|
84
|
-
type: 'single_field',
|
|
85
|
-
label: 'API Key',
|
|
86
|
-
description: 'Single-field API key credential',
|
|
87
|
-
fields: [
|
|
88
|
-
{
|
|
89
|
-
key: 'apiKey',
|
|
90
|
-
label: 'API Key',
|
|
91
|
-
type: 'password',
|
|
92
|
-
required: true,
|
|
93
|
-
placeholder: 'Enter your API key',
|
|
94
|
-
description: 'API key for the service'
|
|
95
|
-
}
|
|
96
|
-
],
|
|
97
|
-
nameSuggestions: ['service-prod', 'service-dev', 'api-key']
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
'webhook-secret': {
|
|
101
|
-
type: 'single_field',
|
|
102
|
-
label: 'Webhook Secret',
|
|
103
|
-
description: 'Webhook signing secret for signature validation',
|
|
104
|
-
fields: [
|
|
105
|
-
{
|
|
106
|
-
key: 'signingSecret',
|
|
107
|
-
label: 'Signing Secret',
|
|
108
|
-
type: 'password',
|
|
109
|
-
required: true,
|
|
110
|
-
placeholder: 'whsec_...',
|
|
111
|
-
description: 'Webhook signing secret from provider dashboard'
|
|
112
|
-
}
|
|
113
|
-
],
|
|
114
|
-
nameSuggestions: ['my-org-cal-com-webhook', 'my-org-stripe-webhook', 'my-org-signature-api-webhook']
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
'api-key-secret': {
|
|
118
|
-
type: 'api-key-secret',
|
|
119
|
-
label: 'API Key + Secret',
|
|
120
|
-
description: 'API key and secret pair authentication',
|
|
121
|
-
fields: [
|
|
122
|
-
{
|
|
123
|
-
key: 'apiKey',
|
|
124
|
-
label: 'API Key',
|
|
125
|
-
type: 'password',
|
|
126
|
-
required: true
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
key: 'apiSecret',
|
|
130
|
-
label: 'API Secret',
|
|
131
|
-
type: 'password',
|
|
132
|
-
required: true
|
|
133
|
-
}
|
|
134
|
-
],
|
|
135
|
-
nameSuggestions: []
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Credential field definition
|
|
5
|
+
*/
|
|
6
|
+
export interface CredentialField {
|
|
7
|
+
key: string
|
|
8
|
+
label: string
|
|
9
|
+
type: 'password' | 'text'
|
|
10
|
+
required: boolean
|
|
11
|
+
placeholder?: string
|
|
12
|
+
description?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Credential schema definition
|
|
17
|
+
*/
|
|
18
|
+
export interface CredentialSchema {
|
|
19
|
+
type: string
|
|
20
|
+
label: string
|
|
21
|
+
description: string
|
|
22
|
+
fields?: CredentialField[]
|
|
23
|
+
docsUrl?: string
|
|
24
|
+
nameSuggestions: string[]
|
|
25
|
+
oauthProvider?: string // For OAuth types
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Zod schemas for runtime validation
|
|
30
|
+
*/
|
|
31
|
+
const CredentialFieldSchema = z.object({
|
|
32
|
+
key: z.string(),
|
|
33
|
+
label: z.string(),
|
|
34
|
+
type: z.enum(['password', 'text']),
|
|
35
|
+
required: z.boolean(),
|
|
36
|
+
placeholder: z.string().optional(),
|
|
37
|
+
description: z.string().optional()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const CredentialSchemaZod = z.object({
|
|
41
|
+
type: z.string(),
|
|
42
|
+
label: z.string(),
|
|
43
|
+
description: z.string(),
|
|
44
|
+
fields: z.array(CredentialFieldSchema).optional(),
|
|
45
|
+
docsUrl: z.string().url().optional(),
|
|
46
|
+
nameSuggestions: z.array(z.string()),
|
|
47
|
+
oauthProvider: z.string().optional()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Credential Schema Registry
|
|
52
|
+
*
|
|
53
|
+
* Add new integration = add schema object here (no modal changes needed)
|
|
54
|
+
*
|
|
55
|
+
* Schema types:
|
|
56
|
+
* - oauth: OAuth-based authentication (handled via OAuth flow)
|
|
57
|
+
* - single_field: Single API key field (generic)
|
|
58
|
+
*/
|
|
59
|
+
export const CREDENTIAL_SCHEMAS: Record<string, CredentialSchema> = {
|
|
60
|
+
'google-sheets': {
|
|
61
|
+
type: 'oauth',
|
|
62
|
+
label: 'Google Sheets',
|
|
63
|
+
description: 'Google Sheets OAuth integration',
|
|
64
|
+
oauthProvider: 'google-sheets',
|
|
65
|
+
nameSuggestions: ['google-sheets-prod', 'google-sheets-dev', 'sheets-workspace']
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
dropbox: {
|
|
69
|
+
type: 'oauth',
|
|
70
|
+
label: 'Dropbox',
|
|
71
|
+
description: 'Dropbox OAuth integration',
|
|
72
|
+
oauthProvider: 'dropbox',
|
|
73
|
+
nameSuggestions: ['dropbox-prod', 'dropbox-dev', 'elevasis-dropbox']
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
oauth: {
|
|
77
|
+
type: 'oauth',
|
|
78
|
+
label: 'OAuth',
|
|
79
|
+
description: 'Generic OAuth credential',
|
|
80
|
+
nameSuggestions: []
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
'api-key': {
|
|
84
|
+
type: 'single_field',
|
|
85
|
+
label: 'API Key',
|
|
86
|
+
description: 'Single-field API key credential',
|
|
87
|
+
fields: [
|
|
88
|
+
{
|
|
89
|
+
key: 'apiKey',
|
|
90
|
+
label: 'API Key',
|
|
91
|
+
type: 'password',
|
|
92
|
+
required: true,
|
|
93
|
+
placeholder: 'Enter your API key',
|
|
94
|
+
description: 'API key for the service'
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
nameSuggestions: ['service-prod', 'service-dev', 'api-key']
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
'webhook-secret': {
|
|
101
|
+
type: 'single_field',
|
|
102
|
+
label: 'Webhook Secret',
|
|
103
|
+
description: 'Webhook signing secret for signature validation',
|
|
104
|
+
fields: [
|
|
105
|
+
{
|
|
106
|
+
key: 'signingSecret',
|
|
107
|
+
label: 'Signing Secret',
|
|
108
|
+
type: 'password',
|
|
109
|
+
required: true,
|
|
110
|
+
placeholder: 'whsec_...',
|
|
111
|
+
description: 'Webhook signing secret from provider dashboard'
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
nameSuggestions: ['my-org-cal-com-webhook', 'my-org-stripe-webhook', 'my-org-signature-api-webhook']
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
'api-key-secret': {
|
|
118
|
+
type: 'api-key-secret',
|
|
119
|
+
label: 'API Key + Secret',
|
|
120
|
+
description: 'API key and secret pair authentication',
|
|
121
|
+
fields: [
|
|
122
|
+
{
|
|
123
|
+
key: 'apiKey',
|
|
124
|
+
label: 'API Key',
|
|
125
|
+
type: 'password',
|
|
126
|
+
required: true
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
key: 'apiSecret',
|
|
130
|
+
label: 'API Secret',
|
|
131
|
+
type: 'password',
|
|
132
|
+
required: true
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
nameSuggestions: []
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
apify: {
|
|
139
|
+
type: 'single_field',
|
|
140
|
+
label: 'Apify',
|
|
141
|
+
description: 'Apify API token for running web-scraping actors (e.g. website crawl, Google Places scrape).',
|
|
142
|
+
fields: [
|
|
143
|
+
{
|
|
144
|
+
key: 'apiKey',
|
|
145
|
+
label: 'API Token',
|
|
146
|
+
type: 'password',
|
|
147
|
+
required: true,
|
|
148
|
+
placeholder: 'apify_api_...',
|
|
149
|
+
description: 'Personal API token from https://console.apify.com/account/integrations'
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
docsUrl: 'https://docs.apify.com/platform/integrations/api',
|
|
153
|
+
nameSuggestions: ['elevasis-apify', 'apify-prod', 'apify-dev']
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
clickup: {
|
|
157
|
+
type: 'single_field',
|
|
158
|
+
label: 'ClickUp',
|
|
159
|
+
description: 'ClickUp personal API token for creating tasks in tenant-managed ClickUp workspaces.',
|
|
160
|
+
fields: [
|
|
161
|
+
{
|
|
162
|
+
key: 'apiToken',
|
|
163
|
+
label: 'API Token',
|
|
164
|
+
type: 'password',
|
|
165
|
+
required: true,
|
|
166
|
+
placeholder: 'pk_...',
|
|
167
|
+
description: 'Personal API token from ClickUp settings. Personal tokens start with pk_.'
|
|
168
|
+
}
|
|
169
|
+
],
|
|
170
|
+
docsUrl: 'https://developer.clickup.com/docs/authentication',
|
|
171
|
+
nameSuggestions: ['clickup-demo', 'clickup-prod', 'clickup-dev']
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get credential schema by type
|
|
177
|
+
* @param type - Credential type identifier
|
|
178
|
+
* @returns Validated credential schema
|
|
179
|
+
* @throws ZodError if schema is malformed
|
|
180
|
+
*/
|
|
181
|
+
export function getCredentialSchema(type: string): CredentialSchema {
|
|
182
|
+
const schema = CREDENTIAL_SCHEMAS[type] || CREDENTIAL_SCHEMAS['api-key']
|
|
183
|
+
return CredentialSchemaZod.parse(schema)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* List all available credential types (for frontend dropdown)
|
|
188
|
+
* @returns Array of credential schemas
|
|
189
|
+
*/
|
|
190
|
+
export function listCredentialSchemas(): CredentialSchema[] {
|
|
191
|
+
return Object.values(CREDENTIAL_SCHEMAS)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get all credential type identifiers
|
|
196
|
+
* @returns Array of type strings
|
|
197
|
+
*/
|
|
198
|
+
export function getCredentialTypes(): string[] {
|
|
199
|
+
return Object.keys(CREDENTIAL_SCHEMAS)
|
|
200
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import { buildOrganizationGraph } from '../graph/build'
|
|
3
|
+
import { CAPABILITY_REGISTRY } from '../domains/prospecting'
|
|
3
4
|
import { resolveOrganizationModel } from '../resolve'
|
|
4
5
|
|
|
5
6
|
describe('organization graph', () => {
|
|
@@ -11,7 +12,9 @@ describe('organization graph', () => {
|
|
|
11
12
|
sourceId: 'sales.crm',
|
|
12
13
|
label: 'CRM'
|
|
13
14
|
})
|
|
14
|
-
expect(
|
|
15
|
+
expect(
|
|
16
|
+
graph.edges.find((edge) => edge.sourceId === 'feature:sales' && edge.targetId === 'feature:sales.crm')
|
|
17
|
+
).toMatchObject({
|
|
15
18
|
kind: 'contains'
|
|
16
19
|
})
|
|
17
20
|
})
|
|
@@ -74,9 +77,112 @@ describe('organization graph', () => {
|
|
|
74
77
|
kind: 'resource',
|
|
75
78
|
label: 'missing-resource'
|
|
76
79
|
})
|
|
77
|
-
expect(graph.edges.find((edge) => edge.
|
|
80
|
+
expect(graph.edges.find((edge) => edge.kind === 'references' && edge.relationshipType === 'uses')).toMatchObject({
|
|
78
81
|
kind: 'references',
|
|
79
82
|
relationshipType: 'uses'
|
|
80
83
|
})
|
|
81
84
|
})
|
|
85
|
+
|
|
86
|
+
it('projects stage nodes from prospecting lifecycle stages', () => {
|
|
87
|
+
const graph = buildOrganizationGraph({
|
|
88
|
+
organizationModel: resolveOrganizationModel({
|
|
89
|
+
features: [{ id: 'operations', label: 'Operations', enabled: true, path: '/operations' }]
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const stageNodes = graph.nodes.filter((node) => node.kind === 'stage')
|
|
94
|
+
expect(stageNodes.length).toBeGreaterThan(0)
|
|
95
|
+
|
|
96
|
+
const populated = graph.nodes.find((node) => node.id === 'stage:populated')
|
|
97
|
+
expect(populated).toMatchObject({ kind: 'stage', sourceId: 'populated' })
|
|
98
|
+
expect(populated?.label).toBeTruthy()
|
|
99
|
+
|
|
100
|
+
expect(graph.edges.find((edge) => edge.kind === 'contains' && edge.targetId === 'stage:populated')).toMatchObject({
|
|
101
|
+
sourceId: 'organization-model'
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('projects capability nodes from CAPABILITY_REGISTRY with maps_to edges to resources', () => {
|
|
106
|
+
const graph = buildOrganizationGraph({
|
|
107
|
+
organizationModel: resolveOrganizationModel({
|
|
108
|
+
features: [{ id: 'operations', label: 'Operations', enabled: true, path: '/operations' }]
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const capabilityNodes = graph.nodes.filter((node) => node.kind === 'capability')
|
|
113
|
+
expect(capabilityNodes).toHaveLength(CAPABILITY_REGISTRY.length)
|
|
114
|
+
|
|
115
|
+
const sample = CAPABILITY_REGISTRY[0]
|
|
116
|
+
const node = graph.nodes.find((n) => n.id === `capability:${sample.id}`)
|
|
117
|
+
expect(node).toMatchObject({
|
|
118
|
+
kind: 'capability',
|
|
119
|
+
sourceId: sample.id,
|
|
120
|
+
label: sample.label,
|
|
121
|
+
description: sample.description
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
expect(
|
|
125
|
+
graph.edges.find(
|
|
126
|
+
(edge) =>
|
|
127
|
+
edge.kind === 'maps_to' &&
|
|
128
|
+
edge.sourceId === `capability:${sample.id}` &&
|
|
129
|
+
edge.targetId === `resource:${sample.resourceId}`
|
|
130
|
+
)
|
|
131
|
+
).toBeDefined()
|
|
132
|
+
|
|
133
|
+
expect(graph.nodes.find((n) => n.id === `resource:${sample.resourceId}`)).toBeDefined()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('emits uses edges from stages to capabilities for every prospecting build-template step', () => {
|
|
137
|
+
const model = resolveOrganizationModel({
|
|
138
|
+
features: [{ id: 'operations', label: 'Operations', enabled: true, path: '/operations' }]
|
|
139
|
+
})
|
|
140
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
141
|
+
|
|
142
|
+
for (const template of model.prospecting.buildTemplates) {
|
|
143
|
+
for (const step of template.steps) {
|
|
144
|
+
const stageNodeId = `stage:${step.stageKey}`
|
|
145
|
+
const capNodeId = `capability:${step.capabilityKey}`
|
|
146
|
+
expect(
|
|
147
|
+
graph.edges.find(
|
|
148
|
+
(edge) =>
|
|
149
|
+
edge.kind === 'uses' &&
|
|
150
|
+
edge.sourceId === stageNodeId &&
|
|
151
|
+
edge.targetId === capNodeId &&
|
|
152
|
+
edge.id.includes(step.id)
|
|
153
|
+
),
|
|
154
|
+
`${template.id}.${step.id}: ${stageNodeId} uses ${capNodeId}`
|
|
155
|
+
).toBeDefined()
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('emits references edges between stages for every step dependsOn link', () => {
|
|
161
|
+
const model = resolveOrganizationModel({
|
|
162
|
+
features: [{ id: 'operations', label: 'Operations', enabled: true, path: '/operations' }]
|
|
163
|
+
})
|
|
164
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
165
|
+
|
|
166
|
+
for (const template of model.prospecting.buildTemplates) {
|
|
167
|
+
const stepById = new Map(template.steps.map((s) => [s.id, s]))
|
|
168
|
+
for (const step of template.steps) {
|
|
169
|
+
for (const depId of step.dependsOn ?? []) {
|
|
170
|
+
const depStep = stepById.get(depId)
|
|
171
|
+
if (!depStep) continue
|
|
172
|
+
const stageNodeId = `stage:${step.stageKey}`
|
|
173
|
+
const depStageNodeId = `stage:${depStep.stageKey}`
|
|
174
|
+
expect(
|
|
175
|
+
graph.edges.find(
|
|
176
|
+
(edge) =>
|
|
177
|
+
edge.kind === 'references' &&
|
|
178
|
+
edge.sourceId === stageNodeId &&
|
|
179
|
+
edge.targetId === depStageNodeId &&
|
|
180
|
+
edge.id.includes(step.id)
|
|
181
|
+
),
|
|
182
|
+
`${template.id}.${step.id} dependsOn ${depId}`
|
|
183
|
+
).toBeDefined()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
})
|
|
82
188
|
})
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import { LEAD_GEN_STAGE_CATALOG } from '../domains/sales'
|
|
3
|
-
import {
|
|
4
|
-
CAPABILITY_REGISTRY,
|
|
5
|
-
DEFAULT_ORGANIZATION_MODEL_PROSPECTING,
|
|
6
|
-
PROSPECTING_STEPS
|
|
7
|
-
} from '../domains/prospecting'
|
|
3
|
+
import { CAPABILITY_REGISTRY, DEFAULT_ORGANIZATION_MODEL_PROSPECTING, PROSPECTING_STEPS } from '../domains/prospecting'
|
|
8
4
|
|
|
9
|
-
const
|
|
5
|
+
const EXPECTED_CAPABILITY_RESOURCE_BY_ID: Record<string, string> = {
|
|
10
6
|
'lead-gen.company.source': 'lgn-import-workflow',
|
|
11
7
|
'lead-gen.company.apollo-import': 'lgn-01c-apollo-import-workflow',
|
|
8
|
+
'lead-gen.company.apify-crawl': 'lgn-02a-apify-website-crawl-workflow',
|
|
12
9
|
'lead-gen.contact.discover': 'lgn-04-email-discovery-workflow',
|
|
13
10
|
'lead-gen.contact.verify-email': 'lgn-05-email-verification-workflow',
|
|
14
11
|
'lead-gen.company.website-extract': 'lgn-02-website-extract-workflow',
|
|
@@ -28,7 +25,9 @@ describe('prospecting organization-model SSOT', () => {
|
|
|
28
25
|
label: 'Decision-makers found',
|
|
29
26
|
description: 'Decision-maker contacts discovered and attached to a qualified company.',
|
|
30
27
|
order: 6,
|
|
31
|
-
entity: 'company'
|
|
28
|
+
entity: 'company',
|
|
29
|
+
recordEntity: 'contact',
|
|
30
|
+
recordStageKey: 'discovered'
|
|
32
31
|
})
|
|
33
32
|
})
|
|
34
33
|
|
|
@@ -56,7 +55,7 @@ describe('prospecting organization-model SSOT', () => {
|
|
|
56
55
|
})
|
|
57
56
|
|
|
58
57
|
it('uses registered capabilities for every prospecting step', () => {
|
|
59
|
-
const capabilityKeys = new Set(
|
|
58
|
+
const capabilityKeys = new Set(CAPABILITY_REGISTRY.map((c) => c.id))
|
|
60
59
|
|
|
61
60
|
for (const templateSteps of Object.values(PROSPECTING_STEPS)) {
|
|
62
61
|
for (const step of Object.values(templateSteps)) {
|
|
@@ -67,12 +66,12 @@ describe('prospecting organization-model SSOT', () => {
|
|
|
67
66
|
|
|
68
67
|
it('derives prospecting lifecycle stages from the stage catalog by entity', () => {
|
|
69
68
|
const expectedCompanyStages = Object.values(LEAD_GEN_STAGE_CATALOG)
|
|
70
|
-
.filter((stage) => stage.entity === 'company')
|
|
69
|
+
.filter((stage) => stage.entity === 'company' || stage.additionalEntities?.includes('company'))
|
|
71
70
|
.sort((a, b) => a.order - b.order)
|
|
72
71
|
.map(({ key, label, order }) => ({ id: key, label, order }))
|
|
73
72
|
|
|
74
73
|
const expectedContactStages = Object.values(LEAD_GEN_STAGE_CATALOG)
|
|
75
|
-
.filter((stage) => stage.entity === 'contact')
|
|
74
|
+
.filter((stage) => stage.entity === 'contact' || stage.additionalEntities?.includes('contact'))
|
|
76
75
|
.sort((a, b) => a.order - b.order)
|
|
77
76
|
.map(({ key, label, order }) => ({ id: key, label, order }))
|
|
78
77
|
|
|
@@ -88,7 +87,8 @@ describe('prospecting organization-model SSOT', () => {
|
|
|
88
87
|
})
|
|
89
88
|
|
|
90
89
|
it('covers all known lead-gen capability registry entries', () => {
|
|
91
|
-
expect(CAPABILITY_REGISTRY).
|
|
92
|
-
|
|
90
|
+
expect(CAPABILITY_REGISTRY).toHaveLength(13)
|
|
91
|
+
const actualResourceById = Object.fromEntries(CAPABILITY_REGISTRY.map((c) => [c.id, c.resourceId]))
|
|
92
|
+
expect(actualResourceById).toEqual(EXPECTED_CAPABILITY_RESOURCE_BY_ID)
|
|
93
93
|
})
|
|
94
94
|
})
|