@elevasis/core 0.25.0 → 0.26.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 +166 -85
- package/dist/index.js +146 -1346
- package/dist/knowledge/index.d.ts +27 -38
- package/dist/knowledge/index.js +1 -1
- package/dist/organization-model/index.d.ts +166 -85
- package/dist/organization-model/index.js +146 -1346
- package/dist/test-utils/index.d.ts +23 -31
- package/dist/test-utils/index.js +75 -1238
- package/package.json +1 -1
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +14 -2
- package/src/business/acquisition/api-schemas.test.ts +70 -77
- package/src/business/acquisition/api-schemas.ts +21 -42
- package/src/business/acquisition/derive-actions.test.ts +11 -21
- package/src/business/acquisition/derive-actions.ts +61 -14
- package/src/business/acquisition/ontology-validation.ts +4 -4
- package/src/business/acquisition/types.ts +7 -8
- package/src/knowledge/queries.ts +0 -1
- package/src/organization-model/__tests__/content-kinds-registry.test.ts +35 -210
- package/src/organization-model/__tests__/defaults.test.ts +4 -4
- package/src/organization-model/__tests__/domains/actions.test.ts +12 -36
- package/src/organization-model/__tests__/domains/offerings.test.ts +13 -6
- package/src/organization-model/__tests__/domains/resources.test.ts +497 -350
- package/src/organization-model/__tests__/domains/systems.test.ts +6 -7
- package/src/organization-model/__tests__/flatten-additive-merge.test.ts +68 -80
- package/src/organization-model/__tests__/foundation.test.ts +81 -14
- package/src/organization-model/__tests__/graph.test.ts +662 -694
- package/src/organization-model/__tests__/knowledge.test.ts +31 -17
- package/src/organization-model/__tests__/lookup-helpers.test.ts +128 -438
- package/src/organization-model/__tests__/migration-helpers.test.ts +362 -591
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +68 -103
- package/src/organization-model/__tests__/published-zero-leak.test.ts +17 -0
- package/src/organization-model/__tests__/recursive-system-schema.test.ts +159 -532
- package/src/organization-model/__tests__/resolve.test.ts +79 -42
- package/src/organization-model/__tests__/schema.test.ts +65 -56
- package/src/organization-model/catalogs/lead-gen.ts +0 -103
- package/src/organization-model/defaults.ts +17 -702
- package/src/organization-model/domains/actions.ts +116 -333
- package/src/organization-model/domains/knowledge.ts +15 -7
- package/src/organization-model/domains/projects.ts +4 -4
- package/src/organization-model/domains/prospecting.ts +405 -395
- package/src/organization-model/domains/resources.ts +206 -135
- package/src/organization-model/domains/sales.ts +5 -5
- package/src/organization-model/domains/systems.ts +8 -23
- package/src/organization-model/graph/build.ts +223 -294
- package/src/organization-model/graph/schema.ts +2 -3
- package/src/organization-model/graph/types.ts +12 -14
- package/src/organization-model/helpers.ts +130 -218
- package/src/organization-model/index.ts +104 -124
- package/src/organization-model/migration-helpers.ts +211 -249
- package/src/organization-model/ontology.ts +0 -60
- package/src/organization-model/organization-graph.mdx +4 -5
- package/src/organization-model/organization-model.mdx +1 -1
- package/src/organization-model/published.ts +236 -226
- package/src/organization-model/resolve.ts +4 -5
- package/src/organization-model/schema.ts +610 -704
- package/src/organization-model/types.ts +167 -161
- package/src/platform/registry/__tests__/validation.test.ts +23 -0
- package/src/platform/registry/validation.ts +13 -2
- package/src/reference/_generated/contracts.md +14 -2
- package/src/organization-model/content-kinds/config.ts +0 -36
- package/src/organization-model/content-kinds/index.ts +0 -78
- package/src/organization-model/content-kinds/pipeline.ts +0 -68
- package/src/organization-model/content-kinds/registry.ts +0 -44
- package/src/organization-model/content-kinds/status.ts +0 -71
- package/src/organization-model/content-kinds/template.ts +0 -83
- package/src/organization-model/content-kinds/types.ts +0 -117
package/package.json
CHANGED
|
@@ -306,7 +306,7 @@ export type OrganizationModelSystemLifecycle = z.infer<typeof SystemLifecycleSch
|
|
|
306
306
|
### `OrganizationModelSystemStatus`
|
|
307
307
|
|
|
308
308
|
```typescript
|
|
309
|
-
/** @deprecated Use OrganizationModelSystemLifecycle. Accepted for one publish cycle. */
|
|
309
|
+
/** @deprecated Use OrganizationModelSystemLifecycle. Accepted for one publish cycle. */
|
|
310
310
|
export type OrganizationModelSystemStatus = z.infer<typeof SystemStatusSchema>
|
|
311
311
|
```
|
|
312
312
|
|
|
@@ -376,6 +376,18 @@ export type OrganizationModelResourceGovernanceStatus = z.infer<typeof ResourceG
|
|
|
376
376
|
export type OrganizationModelResourceOntologyBinding = z.infer<typeof ResourceOntologyBindingSchema>
|
|
377
377
|
```
|
|
378
378
|
|
|
379
|
+
### `OrganizationModelContractRef`
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
export type OrganizationModelContractRef = z.infer<typeof ContractRefSchema>
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### `OrganizationModelResourceOntologyBindingContract`
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
export type OrganizationModelResourceOntologyBindingContract = ResourceOntologyBindingContract
|
|
389
|
+
```
|
|
390
|
+
|
|
379
391
|
### `OrganizationModelAgentKind`
|
|
380
392
|
|
|
381
393
|
```typescript
|
|
@@ -625,7 +637,7 @@ export type OrganizationModelBuiltinIconToken = z.infer<typeof OrganizationModel
|
|
|
625
637
|
### `DeepPartial`
|
|
626
638
|
|
|
627
639
|
```typescript
|
|
628
|
-
export type DeepPartial<T> =
|
|
640
|
+
export type DeepPartial<T> =
|
|
629
641
|
T extends Array<infer U> ? Array<DeepPartial<U>> : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T
|
|
630
642
|
```
|
|
631
643
|
|
|
@@ -2,8 +2,7 @@ import { describe, expect, it } from 'vitest'
|
|
|
2
2
|
import {
|
|
3
3
|
CRM_PIPELINE_DEFINITION,
|
|
4
4
|
DEFAULT_CRM_PRIORITY_RULE_CONFIG,
|
|
5
|
-
LEAD_GEN_PIPELINE_DEFINITIONS
|
|
6
|
-
LEAD_GEN_STAGE_CATALOG
|
|
5
|
+
LEAD_GEN_PIPELINE_DEFINITIONS
|
|
7
6
|
} from '../../organization-model/domains/sales'
|
|
8
7
|
import { CrmPriorityOverrideSchema, evaluateCrmDealPriority, resolveCrmPriorityRuleConfig } from './crm-priority'
|
|
9
8
|
import {
|
|
@@ -61,8 +60,7 @@ import {
|
|
|
61
60
|
CRM_STAGE_KEYS_FROM_ONTOLOGY,
|
|
62
61
|
LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID,
|
|
63
62
|
LEAD_GEN_STAGE_KEYS_FROM_ONTOLOGY,
|
|
64
|
-
getLeadGenStageCatalogFromOntology
|
|
65
|
-
isLeadGenActionKey
|
|
63
|
+
getLeadGenStageCatalogFromOntology
|
|
66
64
|
} from './ontology-validation'
|
|
67
65
|
|
|
68
66
|
// ---------------------------------------------------------------------------
|
|
@@ -70,8 +68,10 @@ import {
|
|
|
70
68
|
// ---------------------------------------------------------------------------
|
|
71
69
|
|
|
72
70
|
const VALID_UUID = '00000000-0000-4000-8000-000000000001'
|
|
73
|
-
const ISO_TS = '2026-04-27T12:34:56.000Z'
|
|
74
|
-
const
|
|
71
|
+
const ISO_TS = '2026-04-27T12:34:56.000Z'
|
|
72
|
+
const VALID_COMPANY_STAGE_KEY = 'qualified'
|
|
73
|
+
const VALID_CONTACT_STAGE_KEY = 'verified'
|
|
74
|
+
const PRIORITY = {
|
|
75
75
|
bucketKey: 'waiting' as const,
|
|
76
76
|
rank: 30,
|
|
77
77
|
label: 'Waiting',
|
|
@@ -130,17 +130,14 @@ describe('business ontology validation bridge', () => {
|
|
|
130
130
|
expect(CrmStageKeySchema.options).toEqual(CRM_STAGE_KEYS_FROM_ONTOLOGY)
|
|
131
131
|
})
|
|
132
132
|
|
|
133
|
-
it('keeps lead-gen
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
expect(LEAD_GEN_STAGE_KEYS_FROM_ONTOLOGY).toEqual(legacyStageKeys)
|
|
137
|
-
expect(Object.keys(getLeadGenStageCatalogFromOntology())).toEqual(legacyStageKeys)
|
|
133
|
+
it('keeps the generic core lead-gen catalog bridge vacuous until a model-owned catalog is supplied', () => {
|
|
134
|
+
expect(LEAD_GEN_STAGE_KEYS_FROM_ONTOLOGY).toEqual([])
|
|
135
|
+
expect(getLeadGenStageCatalogFromOntology()).toEqual({})
|
|
138
136
|
})
|
|
139
137
|
|
|
140
|
-
it('
|
|
141
|
-
expect(BUSINESS_ONTOLOGY_VALIDATION_INDEX.actionTypesByLegacyId['send_reply']
|
|
142
|
-
expect(
|
|
143
|
-
expect(isLeadGenActionKey('lead-gen.missing.action')).toBe(false)
|
|
138
|
+
it('does not publish Elevasis lead-gen action ids through the generic core ontology bridge', () => {
|
|
139
|
+
expect(BUSINESS_ONTOLOGY_VALIDATION_INDEX.actionTypesByLegacyId['send_reply']).toBeUndefined()
|
|
140
|
+
expect(BUSINESS_ONTOLOGY_VALIDATION_INDEX.actionTypesByLegacyId['lead-gen.company.source']).toBeUndefined()
|
|
144
141
|
})
|
|
145
142
|
})
|
|
146
143
|
|
|
@@ -273,11 +270,11 @@ describe('CrmTransitionItemRequestSchema', () => {
|
|
|
273
270
|
}
|
|
274
271
|
})
|
|
275
272
|
|
|
276
|
-
it('
|
|
277
|
-
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, pipelineKey: 'lead-gen' }).success).toBe(
|
|
278
|
-
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stageKey: 'unknown_stage' }).success).toBe(false)
|
|
279
|
-
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stateKey: 'unknown_state' }).success).toBe(false)
|
|
280
|
-
})
|
|
273
|
+
it('accepts generic non-empty pipeline keys but rejects unknown CRM stage/state keys', () => {
|
|
274
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, pipelineKey: 'lead-gen' }).success).toBe(true)
|
|
275
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stageKey: 'unknown_stage' }).success).toBe(false)
|
|
276
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stateKey: 'unknown_state' }).success).toBe(false)
|
|
277
|
+
})
|
|
281
278
|
})
|
|
282
279
|
|
|
283
280
|
// ---------------------------------------------------------------------------
|
|
@@ -550,13 +547,13 @@ describe('UpdateContactRequestSchema', () => {
|
|
|
550
547
|
)
|
|
551
548
|
})
|
|
552
549
|
|
|
553
|
-
it('accepts processingState keyed by the stage catalog', () => {
|
|
554
|
-
const result = UpdateContactRequestSchema.safeParse({
|
|
555
|
-
processingState: {
|
|
556
|
-
[
|
|
557
|
-
status: 'no_result'
|
|
558
|
-
}
|
|
559
|
-
}
|
|
550
|
+
it('accepts processingState keyed by the stage catalog', () => {
|
|
551
|
+
const result = UpdateContactRequestSchema.safeParse({
|
|
552
|
+
processingState: {
|
|
553
|
+
[VALID_CONTACT_STAGE_KEY]: {
|
|
554
|
+
status: 'no_result'
|
|
555
|
+
}
|
|
556
|
+
}
|
|
560
557
|
})
|
|
561
558
|
|
|
562
559
|
expect(result.success).toBe(true)
|
|
@@ -1027,20 +1024,16 @@ describe('ListStatusSchema', () => {
|
|
|
1027
1024
|
// PipelineStageSchema
|
|
1028
1025
|
// ---------------------------------------------------------------------------
|
|
1029
1026
|
|
|
1030
|
-
describe('PipelineStageSchema', () => {
|
|
1031
|
-
it('accepts
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
if (!result.success) {
|
|
1041
|
-
expect(result.error.issues[0]?.message).toMatch(/LEAD_GEN_STAGE_CATALOG/)
|
|
1042
|
-
}
|
|
1043
|
-
})
|
|
1027
|
+
describe('PipelineStageSchema', () => {
|
|
1028
|
+
it('accepts non-empty model-owned stage keys', () => {
|
|
1029
|
+
expect(PipelineStageSchema.safeParse({ key: VALID_COMPANY_STAGE_KEY }).success).toBe(true)
|
|
1030
|
+
expect(PipelineStageSchema.safeParse({ key: 'tenant-custom-stage' }).success).toBe(true)
|
|
1031
|
+
})
|
|
1032
|
+
|
|
1033
|
+
it('rejects an empty stage key', () => {
|
|
1034
|
+
const result = PipelineStageSchema.safeParse({ key: '' })
|
|
1035
|
+
expect(result.success).toBe(false)
|
|
1036
|
+
})
|
|
1044
1037
|
|
|
1045
1038
|
it('accepts optional label, enabled, and order fields', () => {
|
|
1046
1039
|
expect(PipelineStageSchema.safeParse({ key: 'scraped', label: 'Scraped', enabled: true, order: 1 }).success).toBe(
|
|
@@ -1061,23 +1054,23 @@ describe('BuildPlanSnapshotSchema', () => {
|
|
|
1061
1054
|
expect(BuildPlanSnapshotSchema.safeParse(validSnapshot).success).toBe(true)
|
|
1062
1055
|
})
|
|
1063
1056
|
|
|
1064
|
-
it('
|
|
1065
|
-
const result = BuildPlanSnapshotSchema.safeParse({
|
|
1066
|
-
...validSnapshot,
|
|
1067
|
-
steps: [{ ...validSnapshot!.steps[0], stageKey: 'made-up-stage' }]
|
|
1068
|
-
})
|
|
1069
|
-
|
|
1070
|
-
expect(result.success).toBe(
|
|
1071
|
-
})
|
|
1072
|
-
|
|
1073
|
-
it('
|
|
1074
|
-
const result = BuildPlanSnapshotSchema.safeParse({
|
|
1075
|
-
...validSnapshot,
|
|
1076
|
-
steps: [{ ...validSnapshot!.steps[0], actionKey: 'lead-gen.missing.action' }]
|
|
1077
|
-
})
|
|
1078
|
-
|
|
1079
|
-
expect(result.success).toBe(
|
|
1080
|
-
})
|
|
1057
|
+
it('accepts custom non-empty step stage keys at the transport boundary', () => {
|
|
1058
|
+
const result = BuildPlanSnapshotSchema.safeParse({
|
|
1059
|
+
...validSnapshot,
|
|
1060
|
+
steps: [{ ...validSnapshot!.steps[0], stageKey: 'made-up-stage' }]
|
|
1061
|
+
})
|
|
1062
|
+
|
|
1063
|
+
expect(result.success).toBe(true)
|
|
1064
|
+
})
|
|
1065
|
+
|
|
1066
|
+
it('accepts custom non-empty action keys at the transport boundary', () => {
|
|
1067
|
+
const result = BuildPlanSnapshotSchema.safeParse({
|
|
1068
|
+
...validSnapshot,
|
|
1069
|
+
steps: [{ ...validSnapshot!.steps[0], actionKey: 'lead-gen.missing.action' }]
|
|
1070
|
+
})
|
|
1071
|
+
|
|
1072
|
+
expect(result.success).toBe(true)
|
|
1073
|
+
})
|
|
1081
1074
|
|
|
1082
1075
|
it('rejects duplicate step ids', () => {
|
|
1083
1076
|
const first = validSnapshot!.steps[0]!
|
|
@@ -1475,13 +1468,13 @@ describe('UpdateCompanyRequestSchema', () => {
|
|
|
1475
1468
|
expect(UpdateCompanyRequestSchema.safeParse({ status: 'invalid' }).success).toBe(true)
|
|
1476
1469
|
})
|
|
1477
1470
|
|
|
1478
|
-
it('accepts processingState keyed by the stage catalog', () => {
|
|
1479
|
-
const result = UpdateCompanyRequestSchema.safeParse({
|
|
1480
|
-
processingState: {
|
|
1481
|
-
[
|
|
1482
|
-
status: 'success',
|
|
1483
|
-
data: { score: 92 }
|
|
1484
|
-
}
|
|
1471
|
+
it('accepts processingState keyed by the stage catalog', () => {
|
|
1472
|
+
const result = UpdateCompanyRequestSchema.safeParse({
|
|
1473
|
+
processingState: {
|
|
1474
|
+
[VALID_COMPANY_STAGE_KEY]: {
|
|
1475
|
+
status: 'success',
|
|
1476
|
+
data: { score: 92 }
|
|
1477
|
+
}
|
|
1485
1478
|
}
|
|
1486
1479
|
})
|
|
1487
1480
|
|
|
@@ -1492,14 +1485,14 @@ describe('UpdateCompanyRequestSchema', () => {
|
|
|
1492
1485
|
expect(UpdateCompanyRequestSchema.safeParse({ pipelineStatus: 'emailed' }).success).toBe(true)
|
|
1493
1486
|
})
|
|
1494
1487
|
|
|
1495
|
-
it('rejects processingState keys
|
|
1496
|
-
expect(
|
|
1497
|
-
UpdateCompanyRequestSchema.safeParse({
|
|
1498
|
-
processingState: {
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1501
|
-
}).success
|
|
1502
|
-
).toBe(false)
|
|
1488
|
+
it('rejects empty processingState keys', () => {
|
|
1489
|
+
expect(
|
|
1490
|
+
UpdateCompanyRequestSchema.safeParse({
|
|
1491
|
+
processingState: {
|
|
1492
|
+
'': { status: 'success' }
|
|
1493
|
+
}
|
|
1494
|
+
}).success
|
|
1495
|
+
).toBe(false)
|
|
1503
1496
|
})
|
|
1504
1497
|
|
|
1505
1498
|
it('accepts numEmployees of 0', () => {
|
|
@@ -1744,10 +1737,10 @@ describe('ListRecordsQuerySchema', () => {
|
|
|
1744
1737
|
expect(ListRecordsQuerySchema.safeParse({ entity: 'company', stage: 'uploaded' }).success).toBe(true)
|
|
1745
1738
|
})
|
|
1746
1739
|
|
|
1747
|
-
it('
|
|
1748
|
-
expect(ListRecordsQuerySchema.safeParse({ entity: 'contact', stage: 'qualified' }).success).toBe(
|
|
1749
|
-
})
|
|
1750
|
-
})
|
|
1740
|
+
it('accepts stage/entity combinations at the transport boundary', () => {
|
|
1741
|
+
expect(ListRecordsQuerySchema.safeParse({ entity: 'contact', stage: 'qualified' }).success).toBe(true)
|
|
1742
|
+
})
|
|
1743
|
+
})
|
|
1751
1744
|
|
|
1752
1745
|
// ---------------------------------------------------------------------------
|
|
1753
1746
|
// AcqListResponseSchema (forward-compat)
|
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { UuidSchema, NonEmptyStringSchema } from '../../platform/utils/validation'
|
|
3
|
-
import { CRM_PIPELINE_DEFINITION } from '../../organization-model/domains/sales'
|
|
4
3
|
import { CredentialRequirementSchema, RecordColumnConfigSchema } from '../../organization-model/domains/prospecting'
|
|
5
4
|
import { isProspectingBuildTemplateId } from './build-templates'
|
|
6
5
|
import {
|
|
7
6
|
CRM_STAGE_KEYS_FROM_ONTOLOGY,
|
|
8
|
-
CRM_STATE_KEYS_FROM_ONTOLOGY
|
|
9
|
-
isLeadGenActionKey,
|
|
10
|
-
isLeadGenRecordStageValidForEntity,
|
|
11
|
-
isLeadGenStageKey
|
|
7
|
+
CRM_STATE_KEYS_FROM_ONTOLOGY
|
|
12
8
|
} from './ontology-validation'
|
|
13
9
|
export { CrmPriorityBucketKeySchema, CrmPriorityBucketOverrideSchema, CrmPriorityOverrideSchema } from './crm-priority'
|
|
14
10
|
export type { CrmPriorityBucketOverride, CrmPriorityOverride, ResolvedCrmPriorityRuleConfig } from './crm-priority'
|
|
15
11
|
|
|
16
12
|
export const ProcessingStageStatusSchema = z.enum(['success', 'no_result', 'skipped', 'error'])
|
|
17
13
|
|
|
18
|
-
export const LeadGenStageKeySchema = z
|
|
19
|
-
.string()
|
|
20
|
-
.refine((value) => isLeadGenStageKey(value), {
|
|
21
|
-
message: 'processing state key must match LEAD_GEN_STAGE_CATALOG'
|
|
22
|
-
})
|
|
14
|
+
export const LeadGenStageKeySchema = z.string().trim().min(1)
|
|
23
15
|
|
|
24
|
-
export const LeadGenActionKeySchema = z.string().
|
|
25
|
-
message: 'actionKey must match ACTION_REGISTRY'
|
|
26
|
-
})
|
|
16
|
+
export const LeadGenActionKeySchema = z.string().trim().min(1)
|
|
27
17
|
|
|
28
18
|
const crmStageKeys = CRM_STAGE_KEYS_FROM_ONTOLOGY
|
|
29
19
|
const crmStateKeys = CRM_STATE_KEYS_FROM_ONTOLOGY
|
|
@@ -135,9 +125,9 @@ export const TransitionItemRequestSchema = z
|
|
|
135
125
|
})
|
|
136
126
|
.strict()
|
|
137
127
|
|
|
138
|
-
export const CrmTransitionItemRequestSchema = z
|
|
139
|
-
.object({
|
|
140
|
-
pipelineKey: z.
|
|
128
|
+
export const CrmTransitionItemRequestSchema = z
|
|
129
|
+
.object({
|
|
130
|
+
pipelineKey: z.string().min(1),
|
|
141
131
|
stageKey: CrmStageKeySchema,
|
|
142
132
|
stateKey: CrmStateKeySchema.nullable().optional(),
|
|
143
133
|
reason: z.string().optional(),
|
|
@@ -508,14 +498,14 @@ export const IcpRubricSchema = z.object({
|
|
|
508
498
|
})
|
|
509
499
|
|
|
510
500
|
/**
|
|
511
|
-
* One stage entry in a list's `pipeline_config.stages[]`.
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
|
|
501
|
+
* One stage entry in a list's `pipeline_config.stages[]`.
|
|
502
|
+
*
|
|
503
|
+
* Stage catalogs are model-owned. The published core schema validates the
|
|
504
|
+
* transport shape and leaves closed-catalog checks to callers with a resolved
|
|
505
|
+
* Organization Model.
|
|
506
|
+
*/
|
|
515
507
|
export const PipelineStageSchema = z.object({
|
|
516
|
-
key:
|
|
517
|
-
message: 'pipeline stage key must match LEAD_GEN_STAGE_CATALOG'
|
|
518
|
-
}),
|
|
508
|
+
key: LeadGenStageKeySchema,
|
|
519
509
|
label: z.string().optional(),
|
|
520
510
|
enabled: z.boolean().optional(),
|
|
521
511
|
order: z.number().int().optional()
|
|
@@ -1183,25 +1173,14 @@ export const ListMembersQuerySchema = z
|
|
|
1183
1173
|
|
|
1184
1174
|
export const ListRecordEntitySchema = z.enum(['company', 'contact'])
|
|
1185
1175
|
|
|
1186
|
-
export const ListRecordsQuerySchema = z
|
|
1187
|
-
.object({
|
|
1188
|
-
entity: ListRecordEntitySchema,
|
|
1189
|
-
stage: LeadGenStageKeySchema.optional(),
|
|
1190
|
-
limit: z.coerce.number().int().min(1).max(500).default(50),
|
|
1191
|
-
offset: z.coerce.number().int().min(0).default(0)
|
|
1192
|
-
})
|
|
1193
|
-
.strict()
|
|
1194
|
-
.superRefine((query, ctx) => {
|
|
1195
|
-
if (!query.stage) return
|
|
1196
|
-
|
|
1197
|
-
if (!isLeadGenRecordStageValidForEntity(query.stage, query.entity)) {
|
|
1198
|
-
ctx.addIssue({
|
|
1199
|
-
code: z.ZodIssueCode.custom,
|
|
1200
|
-
message: `stage "${query.stage}" is not valid for ${query.entity} records`,
|
|
1201
|
-
path: ['stage']
|
|
1202
|
-
})
|
|
1203
|
-
}
|
|
1204
|
-
})
|
|
1176
|
+
export const ListRecordsQuerySchema = z
|
|
1177
|
+
.object({
|
|
1178
|
+
entity: ListRecordEntitySchema,
|
|
1179
|
+
stage: LeadGenStageKeySchema.optional(),
|
|
1180
|
+
limit: z.coerce.number().int().min(1).max(500).default(50),
|
|
1181
|
+
offset: z.coerce.number().int().min(0).default(0)
|
|
1182
|
+
})
|
|
1183
|
+
.strict()
|
|
1205
1184
|
|
|
1206
1185
|
export const MemberIdParamsSchema = z.object({
|
|
1207
1186
|
memberId: UuidSchema
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { CRM_ACTION_ENTRIES } from '@repo/core/organization-model'
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
3
2
|
import type { AcqDealRow } from './types'
|
|
4
3
|
import type { Action, ActionDef } from './derive-actions'
|
|
5
4
|
import { DEFAULT_CRM_ACTIONS, SendReplyActionPayloadSchema, deriveActions } from './derive-actions'
|
|
6
|
-
import { getCrmActionMetadata } from './ontology-validation'
|
|
7
5
|
|
|
8
6
|
// ---------------------------------------------------------------------------
|
|
9
7
|
// Fixture builder
|
|
@@ -96,28 +94,20 @@ describe('DEFAULT_CRM_ACTIONS static contract', () => {
|
|
|
96
94
|
expect(unique.size).toBe(keys.length)
|
|
97
95
|
})
|
|
98
96
|
|
|
99
|
-
it('derives promoted CRM runtime metadata from
|
|
97
|
+
it('derives promoted CRM runtime metadata from the local runtime contract', () => {
|
|
100
98
|
const sendReply = DEFAULT_CRM_ACTIONS.find((a) => a.key === 'send_reply')
|
|
101
|
-
const metadata = CRM_ACTION_ENTRIES['send_reply']
|
|
102
|
-
const ontologyMetadata = getCrmActionMetadata('send_reply')
|
|
103
99
|
|
|
104
100
|
expect(sendReply).toBeDefined()
|
|
105
|
-
expect(sendReply?.
|
|
106
|
-
expect(sendReply?.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
101
|
+
expect(sendReply?.label).toBe('Send Reply')
|
|
102
|
+
expect(sendReply?.workflowId).toBe('crm-send-reply-workflow')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('keeps mark_no_show as a runtime-only CRM action', () => {
|
|
106
|
+
const markNoShow = DEFAULT_CRM_ACTIONS.find((a) => a.key === 'mark_no_show')
|
|
107
|
+
|
|
108
|
+
expect(markNoShow).toBeDefined()
|
|
109
|
+
expect(markNoShow?.workflowId).toBe('mark_no_show-workflow')
|
|
113
110
|
})
|
|
114
|
-
|
|
115
|
-
it('keeps mark_no_show as a runtime-only CRM action', () => {
|
|
116
|
-
const markNoShow = DEFAULT_CRM_ACTIONS.find((a) => a.key === 'mark_no_show')
|
|
117
|
-
|
|
118
|
-
expect(markNoShow).toBeDefined()
|
|
119
|
-
expect(CRM_ACTION_ENTRIES['mark_no_show']).toBeUndefined()
|
|
120
|
-
})
|
|
121
111
|
})
|
|
122
112
|
|
|
123
113
|
// ---------------------------------------------------------------------------
|
|
@@ -4,9 +4,8 @@ import {
|
|
|
4
4
|
CRM_DISCOVERY_LINK_SENT_STATE,
|
|
5
5
|
CRM_DISCOVERY_NUDGING_STATE,
|
|
6
6
|
CRM_DISCOVERY_REPLIED_STATE
|
|
7
|
-
} from '
|
|
7
|
+
} from '../../organization-model/domains/sales'
|
|
8
8
|
import { getDealOwnership, type DealOwnership } from './deal-ownership'
|
|
9
|
-
import { getCrmActionMetadata } from './ontology-validation'
|
|
10
9
|
import type { AcqDealRow } from './types'
|
|
11
10
|
|
|
12
11
|
export interface Action {
|
|
@@ -34,21 +33,69 @@ export const SendReplyActionPayloadSchema = z
|
|
|
34
33
|
})
|
|
35
34
|
.strict()
|
|
36
35
|
|
|
37
|
-
type CrmActionRuntimeDef = Pick<ActionDef, 'isAvailableFor' | 'payloadSchema'>
|
|
38
|
-
|
|
36
|
+
type CrmActionRuntimeDef = Pick<ActionDef, 'isAvailableFor' | 'payloadSchema'>
|
|
37
|
+
|
|
38
|
+
const CRM_RUNTIME_ACTION_METADATA: Record<string, Pick<ActionDef, 'key' | 'label' | 'workflowId'>> = {
|
|
39
|
+
move_to_proposal: {
|
|
40
|
+
key: 'move_to_proposal',
|
|
41
|
+
label: 'Move to Proposal',
|
|
42
|
+
workflowId: 'move_to_proposal-workflow'
|
|
43
|
+
},
|
|
44
|
+
move_to_closing: {
|
|
45
|
+
key: 'move_to_closing',
|
|
46
|
+
label: 'Move to Closing',
|
|
47
|
+
workflowId: 'move_to_closing-workflow'
|
|
48
|
+
},
|
|
49
|
+
move_to_closed_won: {
|
|
50
|
+
key: 'move_to_closed_won',
|
|
51
|
+
label: 'Close Won',
|
|
52
|
+
workflowId: 'move_to_closed_won-workflow'
|
|
53
|
+
},
|
|
54
|
+
move_to_closed_lost: {
|
|
55
|
+
key: 'move_to_closed_lost',
|
|
56
|
+
label: 'Close Lost',
|
|
57
|
+
workflowId: 'move_to_closed_lost-workflow'
|
|
58
|
+
},
|
|
59
|
+
move_to_nurturing: {
|
|
60
|
+
key: 'move_to_nurturing',
|
|
61
|
+
label: 'Move to Nurturing',
|
|
62
|
+
workflowId: 'move_to_nurturing-workflow'
|
|
63
|
+
},
|
|
64
|
+
send_reply: {
|
|
65
|
+
key: 'send_reply',
|
|
66
|
+
label: 'Send Reply',
|
|
67
|
+
workflowId: 'crm-send-reply-workflow'
|
|
68
|
+
},
|
|
69
|
+
send_link: {
|
|
70
|
+
key: 'send_link',
|
|
71
|
+
label: 'Send Booking Link',
|
|
72
|
+
workflowId: 'crm-send-booking-link-workflow'
|
|
73
|
+
},
|
|
74
|
+
send_nudge: {
|
|
75
|
+
key: 'send_nudge',
|
|
76
|
+
label: 'Send Nudge',
|
|
77
|
+
workflowId: 'crm-send-nudge-workflow'
|
|
78
|
+
},
|
|
79
|
+
rebook: {
|
|
80
|
+
key: 'rebook',
|
|
81
|
+
label: 'Rebook',
|
|
82
|
+
workflowId: 'crm-rebook-workflow'
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
39
86
|
function crmAction(actionId: string, runtime: CrmActionRuntimeDef): ActionDef {
|
|
40
|
-
const metadata =
|
|
41
|
-
if (!metadata?.
|
|
87
|
+
const metadata = CRM_RUNTIME_ACTION_METADATA[actionId]
|
|
88
|
+
if (!metadata?.workflowId) {
|
|
42
89
|
throw new Error(`CRM action metadata for "${actionId}" must define resourceId`)
|
|
43
90
|
}
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
key: metadata.
|
|
47
|
-
label: metadata.label,
|
|
48
|
-
workflowId: metadata.
|
|
49
|
-
...runtime
|
|
50
|
-
}
|
|
51
|
-
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
key: metadata.key,
|
|
94
|
+
label: metadata.label,
|
|
95
|
+
workflowId: metadata.workflowId,
|
|
96
|
+
...runtime
|
|
97
|
+
}
|
|
98
|
+
}
|
|
52
99
|
|
|
53
100
|
export const DEFAULT_CRM_ACTIONS: ActionDef[] = [
|
|
54
101
|
crmAction('move_to_proposal', {
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
import type { OrganizationModel } from '../../organization-model/types'
|
|
12
12
|
import {
|
|
13
13
|
CRM_PIPELINE_DEFINITION,
|
|
14
|
-
LEAD_GEN_STAGE_CATALOG,
|
|
15
14
|
type LeadGenStageCatalogEntry,
|
|
16
15
|
type StatefulStateDefinition
|
|
17
16
|
} from '../../organization-model/domains/sales'
|
|
17
|
+
import { getLeadGenStageCatalog } from '../../organization-model/migration-helpers'
|
|
18
18
|
|
|
19
19
|
export const CRM_PIPELINE_CATALOG_ONTOLOGY_ID = formatOntologyId({
|
|
20
20
|
scope: 'sales.crm',
|
|
@@ -73,14 +73,14 @@ function createCrmPipelineCatalog(): OntologyCatalogType {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function createLeadGenStageCatalog(): OntologyCatalogType {
|
|
76
|
+
function createLeadGenStageCatalog(model: OrganizationModel): OntologyCatalogType {
|
|
77
77
|
return {
|
|
78
78
|
id: LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID,
|
|
79
79
|
label: 'Lead Gen Processing Stages',
|
|
80
80
|
ownerSystemId: 'sales.lead-gen',
|
|
81
81
|
kind: 'processing-stage-catalog',
|
|
82
82
|
entries: Object.fromEntries(
|
|
83
|
-
Object.entries(
|
|
83
|
+
Object.entries(getLeadGenStageCatalog(model)).map(([key, entry]) => [
|
|
84
84
|
key,
|
|
85
85
|
{
|
|
86
86
|
...entry
|
|
@@ -103,7 +103,7 @@ function mergeBridgeCatalogs(model: OrganizationModel): OrganizationModel {
|
|
|
103
103
|
bridgeCatalogTypes[CRM_PIPELINE_CATALOG_ONTOLOGY_ID] = createCrmPipelineCatalog()
|
|
104
104
|
}
|
|
105
105
|
if (baseCatalogTypes[LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID] === undefined) {
|
|
106
|
-
bridgeCatalogTypes[LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID] = createLeadGenStageCatalog()
|
|
106
|
+
bridgeCatalogTypes[LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID] = createLeadGenStageCatalog(model)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
if (Object.keys(bridgeCatalogTypes).length === 0) return model
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { Database } from '../../supabase/database.types'
|
|
2
|
-
import type {
|
|
3
|
-
ActionRegistry,
|
|
4
|
-
CredentialRequirement,
|
|
5
|
-
RecordColumnConfig
|
|
6
|
-
} from '../../organization-model/domains/prospecting'
|
|
7
|
-
import type {
|
|
8
|
-
import type { PipelineStage, ProcessingStageStatus } from './api-schemas'
|
|
2
|
+
import type {
|
|
3
|
+
ActionRegistry,
|
|
4
|
+
CredentialRequirement,
|
|
5
|
+
RecordColumnConfig
|
|
6
|
+
} from '../../organization-model/domains/prospecting'
|
|
7
|
+
import type { PipelineStage, ProcessingStageStatus } from './api-schemas'
|
|
9
8
|
|
|
10
9
|
// =============================================================================
|
|
11
10
|
// Supabase-Generated Types (Re-exported from database.types)
|
|
@@ -55,7 +54,7 @@ export interface WebPost {
|
|
|
55
54
|
aiInsight?: string
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
export type LeadGenStageKey =
|
|
57
|
+
export type LeadGenStageKey = string
|
|
59
58
|
export type LeadGenActionKey = ActionRegistry[number]['id']
|
|
60
59
|
|
|
61
60
|
export interface ProcessingStateEntry {
|
package/src/knowledge/queries.ts
CHANGED
|
@@ -22,7 +22,6 @@ function toTargetGraphNodeId(nodeId: string): string {
|
|
|
22
22
|
nodeId.startsWith('system:') ||
|
|
23
23
|
nodeId.startsWith('knowledge:') ||
|
|
24
24
|
nodeId.startsWith('resource:') ||
|
|
25
|
-
nodeId.startsWith('content-node:') ||
|
|
26
25
|
nodeId.startsWith('ontology:')
|
|
27
26
|
) {
|
|
28
27
|
return nodeId
|