@elevasis/core 0.25.0 → 0.27.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 (80) hide show
  1. package/dist/index.d.ts +171 -90
  2. package/dist/index.js +377 -1541
  3. package/dist/knowledge/index.d.ts +48 -59
  4. package/dist/knowledge/index.js +1 -1
  5. package/dist/organization-model/index.d.ts +171 -90
  6. package/dist/organization-model/index.js +377 -1541
  7. package/dist/test-utils/index.d.ts +25 -33
  8. package/dist/test-utils/index.js +250 -1357
  9. package/package.json +1 -1
  10. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +990 -1065
  11. package/src/business/acquisition/api-schemas.test.ts +1996 -1882
  12. package/src/business/acquisition/api-schemas.ts +1478 -1502
  13. package/src/business/acquisition/crm-next-action.test.ts +45 -25
  14. package/src/business/acquisition/crm-next-action.ts +227 -220
  15. package/src/business/acquisition/crm-priority.test.ts +41 -8
  16. package/src/business/acquisition/crm-priority.ts +365 -349
  17. package/src/business/acquisition/crm-state-actions.test.ts +208 -153
  18. package/src/business/acquisition/derive-actions.test.ts +101 -34
  19. package/src/business/acquisition/derive-actions.ts +8 -92
  20. package/src/business/acquisition/ontology-validation.ts +76 -162
  21. package/src/business/acquisition/types.ts +7 -8
  22. package/src/business/pdf/sections/investment.ts +1 -1
  23. package/src/business/pdf/sections/summary-investment.ts +1 -1
  24. package/src/execution/engine/tools/tool-maps.ts +872 -831
  25. package/src/knowledge/queries.ts +0 -1
  26. package/src/organization-model/__tests__/content-kinds-registry.test.ts +35 -210
  27. package/src/organization-model/__tests__/cross-ref.test.ts +167 -0
  28. package/src/organization-model/__tests__/defaults.test.ts +4 -4
  29. package/src/organization-model/__tests__/domains/actions.test.ts +12 -36
  30. package/src/organization-model/__tests__/domains/offerings.test.ts +13 -6
  31. package/src/organization-model/__tests__/domains/resources.test.ts +497 -350
  32. package/src/organization-model/__tests__/domains/systems.test.ts +6 -7
  33. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +68 -80
  34. package/src/organization-model/__tests__/foundation.test.ts +81 -14
  35. package/src/organization-model/__tests__/graph.test.ts +662 -694
  36. package/src/organization-model/__tests__/knowledge.test.ts +31 -17
  37. package/src/organization-model/__tests__/lookup-helpers.test.ts +128 -438
  38. package/src/organization-model/__tests__/migration-helpers.test.ts +362 -591
  39. package/src/organization-model/__tests__/prospecting-ssot.test.ts +68 -103
  40. package/src/organization-model/__tests__/published-zero-leak.test.ts +76 -0
  41. package/src/organization-model/__tests__/recursive-system-schema.test.ts +159 -532
  42. package/src/organization-model/__tests__/resolve.test.ts +79 -42
  43. package/src/organization-model/__tests__/schema-refinements.test.ts +72 -0
  44. package/src/organization-model/__tests__/schema.test.ts +65 -56
  45. package/src/organization-model/catalogs/lead-gen.ts +0 -103
  46. package/src/organization-model/cross-ref.ts +175 -0
  47. package/src/organization-model/defaults.ts +17 -702
  48. package/src/organization-model/domains/actions.ts +116 -333
  49. package/src/organization-model/domains/branding.ts +6 -6
  50. package/src/organization-model/domains/knowledge.ts +15 -7
  51. package/src/organization-model/domains/projects.ts +4 -4
  52. package/src/organization-model/domains/prospecting.ts +405 -395
  53. package/src/organization-model/domains/resources.ts +206 -135
  54. package/src/organization-model/domains/sales.test.ts +104 -218
  55. package/src/organization-model/domains/sales.ts +217 -380
  56. package/src/organization-model/domains/systems.ts +8 -23
  57. package/src/organization-model/graph/build.ts +223 -294
  58. package/src/organization-model/graph/schema.ts +2 -3
  59. package/src/organization-model/graph/types.ts +12 -14
  60. package/src/organization-model/helpers.ts +130 -218
  61. package/src/organization-model/index.ts +105 -124
  62. package/src/organization-model/migration-helpers.ts +211 -249
  63. package/src/organization-model/ontology.ts +0 -60
  64. package/src/organization-model/organization-graph.mdx +4 -5
  65. package/src/organization-model/organization-model.mdx +1 -1
  66. package/src/organization-model/published.ts +236 -226
  67. package/src/organization-model/resolve.ts +4 -5
  68. package/src/organization-model/schema-refinements.ts +667 -0
  69. package/src/organization-model/schema.ts +88 -889
  70. package/src/organization-model/types.ts +167 -161
  71. package/src/platform/registry/__tests__/validation.test.ts +23 -0
  72. package/src/platform/registry/validation.ts +13 -2
  73. package/src/reference/_generated/contracts.md +990 -1065
  74. package/src/organization-model/content-kinds/config.ts +0 -36
  75. package/src/organization-model/content-kinds/index.ts +0 -78
  76. package/src/organization-model/content-kinds/pipeline.ts +0 -68
  77. package/src/organization-model/content-kinds/registry.ts +0 -44
  78. package/src/organization-model/content-kinds/status.ts +0 -71
  79. package/src/organization-model/content-kinds/template.ts +0 -83
  80. package/src/organization-model/content-kinds/types.ts +0 -117
@@ -1,12 +1,4 @@
1
- import { z } from 'zod'
2
- import {
3
- CRM_DISCOVERY_BOOKING_CANCELLED_STATE,
4
- CRM_DISCOVERY_LINK_SENT_STATE,
5
- CRM_DISCOVERY_NUDGING_STATE,
6
- CRM_DISCOVERY_REPLIED_STATE
7
- } from '@repo/core/organization-model'
8
- import { getDealOwnership, type DealOwnership } from './deal-ownership'
9
- import { getCrmActionMetadata } from './ontology-validation'
1
+ import { z } from 'zod'
10
2
  import type { AcqDealRow } from './types'
11
3
 
12
4
  export interface Action {
@@ -23,10 +15,10 @@ export interface ActionDef {
23
15
  payloadSchema?: z.ZodTypeAny
24
16
  }
25
17
 
26
- type DealActionInput = AcqDealRow & {
27
- ownership?: DealOwnership
28
- nextAction?: string | null
29
- }
18
+ type DealActionInput = AcqDealRow & {
19
+ ownership?: 'us' | 'them' | null
20
+ nextAction?: string | null
21
+ }
30
22
 
31
23
  export const SendReplyActionPayloadSchema = z
32
24
  .object({
@@ -34,85 +26,9 @@ export const SendReplyActionPayloadSchema = z
34
26
  })
35
27
  .strict()
36
28
 
37
- type CrmActionRuntimeDef = Pick<ActionDef, 'isAvailableFor' | 'payloadSchema'>
38
-
39
- function crmAction(actionId: string, runtime: CrmActionRuntimeDef): ActionDef {
40
- const metadata = getCrmActionMetadata(actionId)
41
- if (!metadata?.resourceId) {
42
- throw new Error(`CRM action metadata for "${actionId}" must define resourceId`)
43
- }
44
-
45
- return {
46
- key: metadata.id,
47
- label: metadata.label,
48
- workflowId: metadata.resourceId,
49
- ...runtime
50
- }
51
- }
52
-
53
- export const DEFAULT_CRM_ACTIONS: ActionDef[] = [
54
- crmAction('move_to_proposal', {
55
- isAvailableFor: (deal) => deal.stage_key === 'interested'
56
- }),
57
- crmAction('move_to_closing', {
58
- isAvailableFor: (deal) => deal.stage_key === 'proposal'
59
- }),
60
- crmAction('move_to_closed_won', {
61
- isAvailableFor: (deal) => deal.stage_key === 'closing'
62
- }),
63
- crmAction('move_to_closed_lost', {
64
- isAvailableFor: (deal) =>
65
- deal.stage_key === 'interested' || deal.stage_key === 'proposal' || deal.stage_key === 'closing'
66
- }),
67
- crmAction('move_to_nurturing', {
68
- isAvailableFor: (deal) =>
69
- deal.stage_key === 'interested' || deal.stage_key === 'proposal' || deal.stage_key === 'closing'
70
- }),
71
- crmAction('send_reply', {
72
- isAvailableFor: (deal) =>
73
- deal.stage_key === 'interested' &&
74
- isOurReplyAction(deal) &&
75
- (deal.state_key === CRM_DISCOVERY_REPLIED_STATE.stateKey ||
76
- deal.state_key === CRM_DISCOVERY_LINK_SENT_STATE.stateKey ||
77
- deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey),
78
- payloadSchema: SendReplyActionPayloadSchema
79
- }),
80
- crmAction('send_link', {
81
- isAvailableFor: (deal) =>
82
- deal.stage_key === 'interested' && deal.state_key === CRM_DISCOVERY_REPLIED_STATE.stateKey
83
- }),
84
- crmAction('send_nudge', {
85
- isAvailableFor: (deal) =>
86
- deal.stage_key === 'interested' &&
87
- (deal.state_key === CRM_DISCOVERY_LINK_SENT_STATE.stateKey ||
88
- deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey)
89
- }),
90
- {
91
- key: 'mark_no_show',
92
- label: 'Mark No-Show',
93
- isAvailableFor: (deal) =>
94
- deal.stage_key === 'interested' && deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey,
95
- // Mirrors the auto-timeout precedent in operations/sales/crm/pipeline/timeout-actions.ts:
96
- // both manual-click and timeout move the deal to closed_lost. The action_taken activity
97
- // event captures operator intent and distinguishes the manual variant from the timed one.
98
- workflowId: 'mark_no_show-workflow'
99
- },
100
- crmAction('rebook', {
101
- isAvailableFor: (deal) =>
102
- deal.stage_key === 'interested' && deal.state_key === CRM_DISCOVERY_BOOKING_CANCELLED_STATE.stateKey
103
- })
104
- ]
105
-
106
- export function deriveActions(deal: DealActionInput, actions: ActionDef[] = DEFAULT_CRM_ACTIONS): Action[] {
107
- return actions
108
- .filter((a) => a.isAvailableFor(deal))
29
+ export function deriveActions(deal: DealActionInput, actions: ActionDef[] = []): Action[] {
30
+ return actions
31
+ .filter((a) => a.isAvailableFor(deal))
109
32
  .map(({ key, label, payloadSchema }) => ({ key, label, payloadSchema }))
110
33
  }
111
34
 
112
- function isOurReplyAction(deal: DealActionInput): boolean {
113
- if (Object.prototype.hasOwnProperty.call(deal, 'nextAction')) {
114
- return deal.nextAction === 'send_reply'
115
- }
116
-
117
- return (deal.ownership ?? getDealOwnership(deal)) === 'us'
118
- }
@@ -1,4 +1,3 @@
1
- import { DEFAULT_ORGANIZATION_MODEL } from '../../organization-model/defaults'
2
1
  import {
3
2
  compileOrganizationOntology,
4
3
  formatOntologyId,
@@ -10,11 +9,10 @@ import {
10
9
  } from '../../organization-model/ontology'
11
10
  import type { OrganizationModel } from '../../organization-model/types'
12
11
  import {
13
- CRM_PIPELINE_DEFINITION,
14
- LEAD_GEN_STAGE_CATALOG,
15
12
  type LeadGenStageCatalogEntry,
16
13
  type StatefulStateDefinition
17
14
  } from '../../organization-model/domains/sales'
15
+ import { getLeadGenStageCatalog } from '../../organization-model/migration-helpers'
18
16
 
19
17
  export const CRM_PIPELINE_CATALOG_ONTOLOGY_ID = formatOntologyId({
20
18
  scope: 'sales.crm',
@@ -28,20 +26,6 @@ export const LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID = formatOntologyId({
28
26
  localId: 'lead-gen.stage-catalog'
29
27
  })
30
28
 
31
- const CRM_DEAL_OBJECT_ONTOLOGY_ID = formatOntologyId({
32
- scope: 'sales.crm',
33
- kind: 'object',
34
- localId: 'crm.deal'
35
- })
36
-
37
- type CrmCatalogStageEntry = {
38
- key: string
39
- label: string
40
- order: number
41
- color?: string
42
- states: StatefulStateDefinition[]
43
- }
44
-
45
29
  export type BusinessOntologyValidationIndex = {
46
30
  ontology: ResolvedOntologyIndex
47
31
  crmPipelineCatalog: OntologyCatalogType
@@ -49,38 +33,14 @@ export type BusinessOntologyValidationIndex = {
49
33
  actionTypesByLegacyId: Record<string, OntologyActionType>
50
34
  }
51
35
 
52
- function createCrmPipelineCatalog(): OntologyCatalogType {
53
- return {
54
- id: CRM_PIPELINE_CATALOG_ONTOLOGY_ID,
55
- label: CRM_PIPELINE_DEFINITION.label,
56
- ownerSystemId: 'sales.crm',
57
- kind: 'pipeline',
58
- appliesTo: CRM_DEAL_OBJECT_ONTOLOGY_ID,
59
- entries: Object.fromEntries(
60
- CRM_PIPELINE_DEFINITION.stages.map((stage, index) => [
61
- stage.stageKey,
62
- {
63
- key: stage.stageKey,
64
- label: stage.label,
65
- order: (index + 1) * 10,
66
- ...(stage.color !== undefined ? { color: stage.color } : {}),
67
- states: stage.states.map((state) => ({ ...state }))
68
- } satisfies CrmCatalogStageEntry
69
- ])
70
- ),
71
- legacyPipelineKey: CRM_PIPELINE_DEFINITION.pipelineKey,
72
- legacyEntityKey: CRM_PIPELINE_DEFINITION.entityKey
73
- }
74
- }
75
-
76
- function createLeadGenStageCatalog(): OntologyCatalogType {
36
+ function createLeadGenStageCatalog(model: OrganizationModel): OntologyCatalogType {
77
37
  return {
78
38
  id: LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID,
79
39
  label: 'Lead Gen Processing Stages',
80
40
  ownerSystemId: 'sales.lead-gen',
81
41
  kind: 'processing-stage-catalog',
82
42
  entries: Object.fromEntries(
83
- Object.entries(LEAD_GEN_STAGE_CATALOG).map(([key, entry]) => [
43
+ Object.entries(getLeadGenStageCatalog(model)).map(([key, entry]) => [
84
44
  key,
85
45
  {
86
46
  ...entry
@@ -99,11 +59,10 @@ function mergeBridgeCatalogs(model: OrganizationModel): OrganizationModel {
99
59
  const baseCatalogTypes = model.ontology?.catalogTypes ?? {}
100
60
  const bridgeCatalogTypes: Record<OntologyId, OntologyCatalogType> = {}
101
61
 
102
- if (baseCatalogTypes[CRM_PIPELINE_CATALOG_ONTOLOGY_ID] === undefined) {
103
- bridgeCatalogTypes[CRM_PIPELINE_CATALOG_ONTOLOGY_ID] = createCrmPipelineCatalog()
104
- }
62
+ // CRM pipeline catalog is now authored directly in @repo/elevasis-core canonicalOrganizationModel
63
+ // at 'sales.crm:catalog/crm.pipeline'. No bridge injection needed — the model self-supplies it.
105
64
  if (baseCatalogTypes[LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID] === undefined) {
106
- bridgeCatalogTypes[LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID] = createLeadGenStageCatalog()
65
+ bridgeCatalogTypes[LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID] = createLeadGenStageCatalog(model)
107
66
  }
108
67
 
109
68
  if (Object.keys(bridgeCatalogTypes).length === 0) return model
@@ -120,9 +79,7 @@ function mergeBridgeCatalogs(model: OrganizationModel): OrganizationModel {
120
79
  }
121
80
  }
122
81
 
123
- function compileBusinessOntologyValidationIndex(
124
- model: OrganizationModel = DEFAULT_ORGANIZATION_MODEL
125
- ): BusinessOntologyValidationIndex {
82
+ export function compileBusinessOntologyValidationIndex(model: OrganizationModel): BusinessOntologyValidationIndex {
126
83
  const compilation = compileOrganizationOntology(mergeBridgeCatalogs(model))
127
84
  if (compilation.diagnostics.length > 0) {
128
85
  const summary = compilation.diagnostics.map((diagnostic) => diagnostic.message).join('; ')
@@ -156,34 +113,18 @@ function indexActionTypesByLegacyId(
156
113
  return byLegacyId
157
114
  }
158
115
 
159
- export const BUSINESS_ONTOLOGY_VALIDATION_INDEX = compileBusinessOntologyValidationIndex()
160
-
161
116
  function getCatalogEntries(catalog: OntologyCatalogType): Record<string, unknown> {
162
117
  return isPlainRecord(catalog.entries) ? catalog.entries : {}
163
118
  }
164
119
 
165
- function asCrmStageEntry(key: string, value: unknown): CrmCatalogStageEntry {
166
- const record = isPlainRecord(value) ? value : {}
167
- const label = typeof record.label === 'string' ? record.label : key
168
- const order = typeof record.order === 'number' ? record.order : 0
169
- const color = typeof record.color === 'string' ? record.color : undefined
170
- const rawStates = Array.isArray(record.states) ? record.states : []
171
- const states = rawStates.flatMap((state): StatefulStateDefinition[] => {
172
- if (!isPlainRecord(state) || typeof state.stateKey !== 'string') return []
173
- return [
174
- {
175
- stateKey: state.stateKey,
176
- label: typeof state.label === 'string' ? state.label : state.stateKey
177
- }
178
- ]
179
- })
120
+ function asStatefulStateDefinition(value: unknown): StatefulStateDefinition | undefined {
121
+ if (!isPlainRecord(value) || typeof value.stateKey !== 'string' || typeof value.label !== 'string') {
122
+ return undefined
123
+ }
180
124
 
181
125
  return {
182
- key,
183
- label,
184
- order,
185
- ...(color !== undefined ? { color } : {}),
186
- states
126
+ stateKey: value.stateKey,
127
+ label: value.label
187
128
  }
188
129
  }
189
130
 
@@ -211,99 +152,72 @@ function asLeadGenStageEntry(key: string, value: unknown): LeadGenStageCatalogEn
211
152
  }
212
153
  }
213
154
 
214
- export const CRM_STAGE_KEYS_FROM_ONTOLOGY = Object.keys(
215
- getCatalogEntries(BUSINESS_ONTOLOGY_VALIDATION_INDEX.crmPipelineCatalog)
216
- ) as [string, ...string[]]
217
-
218
- export const CRM_STATE_KEYS_FROM_ONTOLOGY = Object.values(
219
- getCatalogEntries(BUSINESS_ONTOLOGY_VALIDATION_INDEX.crmPipelineCatalog)
220
- ).flatMap((entry, index) => {
221
- const stageKey = CRM_STAGE_KEYS_FROM_ONTOLOGY[index]
222
- if (stageKey === undefined) return []
223
- return asCrmStageEntry(stageKey, entry).states.map((state) => state.stateKey)
224
- }) as [string, ...string[]]
225
-
226
- export const LEAD_GEN_STAGE_KEYS_FROM_ONTOLOGY = Object.keys(
227
- getCatalogEntries(BUSINESS_ONTOLOGY_VALIDATION_INDEX.leadGenStageCatalog)
228
- ) as [string, ...string[]]
229
-
230
- export function getCrmStatesForStage(stageKey: string): StatefulStateDefinition[] {
231
- const entry = getCatalogEntries(BUSINESS_ONTOLOGY_VALIDATION_INDEX.crmPipelineCatalog)[stageKey]
232
- return entry === undefined ? [] : asCrmStageEntry(stageKey, entry).states
233
- }
234
-
235
- export function isCrmStageKey(stageKey: string): boolean {
236
- return Object.prototype.hasOwnProperty.call(
237
- getCatalogEntries(BUSINESS_ONTOLOGY_VALIDATION_INDEX.crmPipelineCatalog),
238
- stageKey
239
- )
240
- }
241
-
242
- export function isCrmStateKey(stateKey: string): boolean {
243
- return CRM_STATE_KEYS_FROM_ONTOLOGY.includes(stateKey)
244
- }
245
-
246
- export function getLeadGenStageCatalogFromOntology(): Record<string, LeadGenStageCatalogEntry> {
247
- return Object.fromEntries(
248
- Object.entries(getCatalogEntries(BUSINESS_ONTOLOGY_VALIDATION_INDEX.leadGenStageCatalog)).map(([key, value]) => [
249
- key,
250
- asLeadGenStageEntry(key, value)
251
- ])
252
- )
253
- }
254
-
255
- export function getLeadGenStageEntry(stageKey: string): LeadGenStageCatalogEntry | undefined {
256
- return getLeadGenStageCatalogFromOntology()[stageKey]
257
- }
258
-
259
- export function isLeadGenStageKey(stageKey: string): boolean {
260
- return getLeadGenStageEntry(stageKey) !== undefined
261
- }
262
-
263
- export function isLeadGenStageValidForEntity(stageKey: string, entity: 'company' | 'contact'): boolean {
264
- const stage = getLeadGenStageEntry(stageKey)
265
- return stage !== undefined && (stage.entity === entity || stage.additionalEntities?.includes(entity) === true)
266
- }
267
-
268
- export function isLeadGenRecordStageValidForEntity(stageKey: string, entity: 'company' | 'contact'): boolean {
269
- const stage = getLeadGenStageEntry(stageKey)
270
- return (
271
- stage !== undefined &&
272
- (stage.entity === entity || stage.additionalEntities?.includes(entity) === true || stage.recordEntity === entity)
273
- )
274
- }
275
-
276
- export function resolveLeadGenRecordStageKey(stageKey: string, entity: 'company' | 'contact'): string {
277
- const stage = getLeadGenStageEntry(stageKey)
278
- return stage?.recordEntity === entity && stage.recordStageKey ? stage.recordStageKey : stageKey
279
- }
280
-
281
- export type BusinessActionMetadata = {
282
- id: string
283
- label: string
284
- resourceId?: string
155
+ /**
156
+ * Returns the valid states for a given CRM stage key.
157
+ * Resolves against the caller-supplied ontology validation index so generic
158
+ * core does not materialize an Elevasis CRM pipeline singleton.
159
+ */
160
+ export function getCrmStatesForStage(
161
+ index: BusinessOntologyValidationIndex,
162
+ stageKey: string
163
+ ): StatefulStateDefinition[] {
164
+ const stage = getCatalogEntries(index.crmPipelineCatalog)[stageKey]
165
+ const states = isPlainRecord(stage) && Array.isArray(stage.states) ? stage.states : []
166
+ return states.flatMap((state) => {
167
+ const parsed = asStatefulStateDefinition(state)
168
+ return parsed ? [parsed] : []
169
+ })
285
170
  }
286
171
 
287
- function getActionMetadata(actionKey: string): BusinessActionMetadata | undefined {
288
- const actionType = BUSINESS_ONTOLOGY_VALIDATION_INDEX.actionTypesByLegacyId[actionKey]
289
- if (actionType === undefined) return undefined
172
+ /**
173
+ * Lead-gen stage validators bound to a specific resolved validation index.
174
+ *
175
+ * The lead-gen stage catalog is tenant-model-dependent (it derives from the
176
+ * organization model's `sales.lead-gen` stage records). Hosts that own a
177
+ * populated tenant model (e.g. `apps/api` with the Elevasis canonical model)
178
+ * MUST build their own index via `compileBusinessOntologyValidationIndex(model)`
179
+ * and call `createLeadGenStageValidators(index)`. `@repo/core` stays generic —
180
+ * it must never import a tenant model.
181
+ */
182
+ export type LeadGenStageValidators = {
183
+ getLeadGenStageCatalog(): Record<string, LeadGenStageCatalogEntry>
184
+ getLeadGenStageEntry(stageKey: string): LeadGenStageCatalogEntry | undefined
185
+ isLeadGenStageKey(stageKey: string): boolean
186
+ isLeadGenStageValidForEntity(stageKey: string, entity: 'company' | 'contact'): boolean
187
+ isLeadGenRecordStageValidForEntity(stageKey: string, entity: 'company' | 'contact'): boolean
188
+ resolveLeadGenRecordStageKey(stageKey: string, entity: 'company' | 'contact'): string
189
+ }
190
+
191
+ export function createLeadGenStageValidators(index: BusinessOntologyValidationIndex): LeadGenStageValidators {
192
+ const getCatalog = (): Record<string, LeadGenStageCatalogEntry> =>
193
+ Object.fromEntries(
194
+ Object.entries(getCatalogEntries(index.leadGenStageCatalog)).map(([key, value]) => [
195
+ key,
196
+ asLeadGenStageEntry(key, value)
197
+ ])
198
+ )
199
+ const getEntry = (stageKey: string): LeadGenStageCatalogEntry | undefined => getCatalog()[stageKey]
290
200
 
291
- const resourceId = actionType['resourceId']
292
201
  return {
293
- id: actionKey,
294
- label: actionType.label ?? actionKey,
295
- ...(typeof resourceId === 'string' ? { resourceId } : {})
202
+ getLeadGenStageCatalog: getCatalog,
203
+ getLeadGenStageEntry: getEntry,
204
+ isLeadGenStageKey: (stageKey) => getEntry(stageKey) !== undefined,
205
+ isLeadGenStageValidForEntity: (stageKey, entity) => {
206
+ const stage = getEntry(stageKey)
207
+ return stage !== undefined && (stage.entity === entity || stage.additionalEntities?.includes(entity) === true)
208
+ },
209
+ isLeadGenRecordStageValidForEntity: (stageKey, entity) => {
210
+ const stage = getEntry(stageKey)
211
+ return (
212
+ stage !== undefined &&
213
+ (stage.entity === entity ||
214
+ stage.additionalEntities?.includes(entity) === true ||
215
+ stage.recordEntity === entity)
216
+ )
217
+ },
218
+ resolveLeadGenRecordStageKey: (stageKey, entity) => {
219
+ const stage = getEntry(stageKey)
220
+ return stage?.recordEntity === entity && stage.recordStageKey ? stage.recordStageKey : stageKey
221
+ }
296
222
  }
297
223
  }
298
-
299
- export function getCrmActionMetadata(actionKey: string): BusinessActionMetadata | undefined {
300
- const actionType = BUSINESS_ONTOLOGY_VALIDATION_INDEX.actionTypesByLegacyId[actionKey]
301
- return actionType?.ownerSystemId === 'sales.crm' ? getActionMetadata(actionKey) : undefined
302
- }
303
-
304
- export function isLeadGenActionKey(actionKey: string): boolean {
305
- return (
306
- actionKey.startsWith('lead-gen.') &&
307
- BUSINESS_ONTOLOGY_VALIDATION_INDEX.actionTypesByLegacyId[actionKey] !== undefined
308
- )
309
- }
@@ -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 { LEAD_GEN_STAGE_CATALOG } from '../../organization-model/domains/sales'
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 = (typeof LEAD_GEN_STAGE_CATALOG)[keyof typeof LEAD_GEN_STAGE_CATALOG]['key']
57
+ export type LeadGenStageKey = string
59
58
  export type LeadGenActionKey = ActionRegistry[number]['id']
60
59
 
61
60
  export interface ProcessingStateEntry {
@@ -35,7 +35,7 @@ export function investmentSection(config: InvestmentConfig): Page {
35
35
  tier,
36
36
  customTierDescription,
37
37
  nextSteps = DEFAULT_NEXT_STEPS,
38
- contactCta = 'Questions? Reply to this email or book a call at elevasis.io/call'
38
+ contactCta = 'Questions? Reply to this email or use the contact link provided by your advisor'
39
39
  } = config
40
40
 
41
41
  const tierDescription = tier === 'custom' && customTierDescription
@@ -42,7 +42,7 @@ export function summaryInvestmentSection(config: SummaryInvestmentConfig): Page
42
42
  initialFee,
43
43
  monthlyFee,
44
44
  nextSteps = DEFAULT_NEXT_STEPS,
45
- contactCta = 'Questions? Reply to this email or book a call at elevasis.io/call'
45
+ contactCta = 'Questions? Reply to this email or use the contact link provided by your advisor'
46
46
  } = config
47
47
 
48
48
  // Calculate totals