@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.
Files changed (66) hide show
  1. package/dist/index.d.ts +166 -85
  2. package/dist/index.js +146 -1346
  3. package/dist/knowledge/index.d.ts +27 -38
  4. package/dist/knowledge/index.js +1 -1
  5. package/dist/organization-model/index.d.ts +166 -85
  6. package/dist/organization-model/index.js +146 -1346
  7. package/dist/test-utils/index.d.ts +23 -31
  8. package/dist/test-utils/index.js +75 -1238
  9. package/package.json +1 -1
  10. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +14 -2
  11. package/src/business/acquisition/api-schemas.test.ts +70 -77
  12. package/src/business/acquisition/api-schemas.ts +21 -42
  13. package/src/business/acquisition/derive-actions.test.ts +11 -21
  14. package/src/business/acquisition/derive-actions.ts +61 -14
  15. package/src/business/acquisition/ontology-validation.ts +4 -4
  16. package/src/business/acquisition/types.ts +7 -8
  17. package/src/knowledge/queries.ts +0 -1
  18. package/src/organization-model/__tests__/content-kinds-registry.test.ts +35 -210
  19. package/src/organization-model/__tests__/defaults.test.ts +4 -4
  20. package/src/organization-model/__tests__/domains/actions.test.ts +12 -36
  21. package/src/organization-model/__tests__/domains/offerings.test.ts +13 -6
  22. package/src/organization-model/__tests__/domains/resources.test.ts +497 -350
  23. package/src/organization-model/__tests__/domains/systems.test.ts +6 -7
  24. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +68 -80
  25. package/src/organization-model/__tests__/foundation.test.ts +81 -14
  26. package/src/organization-model/__tests__/graph.test.ts +662 -694
  27. package/src/organization-model/__tests__/knowledge.test.ts +31 -17
  28. package/src/organization-model/__tests__/lookup-helpers.test.ts +128 -438
  29. package/src/organization-model/__tests__/migration-helpers.test.ts +362 -591
  30. package/src/organization-model/__tests__/prospecting-ssot.test.ts +68 -103
  31. package/src/organization-model/__tests__/published-zero-leak.test.ts +17 -0
  32. package/src/organization-model/__tests__/recursive-system-schema.test.ts +159 -532
  33. package/src/organization-model/__tests__/resolve.test.ts +79 -42
  34. package/src/organization-model/__tests__/schema.test.ts +65 -56
  35. package/src/organization-model/catalogs/lead-gen.ts +0 -103
  36. package/src/organization-model/defaults.ts +17 -702
  37. package/src/organization-model/domains/actions.ts +116 -333
  38. package/src/organization-model/domains/knowledge.ts +15 -7
  39. package/src/organization-model/domains/projects.ts +4 -4
  40. package/src/organization-model/domains/prospecting.ts +405 -395
  41. package/src/organization-model/domains/resources.ts +206 -135
  42. package/src/organization-model/domains/sales.ts +5 -5
  43. package/src/organization-model/domains/systems.ts +8 -23
  44. package/src/organization-model/graph/build.ts +223 -294
  45. package/src/organization-model/graph/schema.ts +2 -3
  46. package/src/organization-model/graph/types.ts +12 -14
  47. package/src/organization-model/helpers.ts +130 -218
  48. package/src/organization-model/index.ts +104 -124
  49. package/src/organization-model/migration-helpers.ts +211 -249
  50. package/src/organization-model/ontology.ts +0 -60
  51. package/src/organization-model/organization-graph.mdx +4 -5
  52. package/src/organization-model/organization-model.mdx +1 -1
  53. package/src/organization-model/published.ts +236 -226
  54. package/src/organization-model/resolve.ts +4 -5
  55. package/src/organization-model/schema.ts +610 -704
  56. package/src/organization-model/types.ts +167 -161
  57. package/src/platform/registry/__tests__/validation.test.ts +23 -0
  58. package/src/platform/registry/validation.ts +13 -2
  59. package/src/reference/_generated/contracts.md +14 -2
  60. package/src/organization-model/content-kinds/config.ts +0 -36
  61. package/src/organization-model/content-kinds/index.ts +0 -78
  62. package/src/organization-model/content-kinds/pipeline.ts +0 -68
  63. package/src/organization-model/content-kinds/registry.ts +0 -44
  64. package/src/organization-model/content-kinds/status.ts +0 -71
  65. package/src/organization-model/content-kinds/template.ts +0 -83
  66. package/src/organization-model/content-kinds/types.ts +0 -117
@@ -4,24 +4,69 @@ import { getSystem, listAllSystems } from '../helpers'
4
4
  import { defineOrganizationModel, resolveOrganizationModel, resolveOrganizationModelWithResources } from '../resolve'
5
5
 
6
6
  describe('organization-model resolve', () => {
7
- it('resolves the system defaults', () => {
8
- const model = resolveOrganizationModel()
9
-
10
- expect(model.version).toBe(1)
11
- expect('surfaces' in model).toBe(false)
12
- expect('navigationGroups' in model).toBe(false)
13
- expect(model.systems['dashboard']?.path).toBe('/')
14
- expect(model.systems['sales']?.path).toBe('/sales')
15
- expect(model.systems['sales.crm']?.path).toBe('/crm')
16
- expect(model.systems['admin']?.requiresAdmin).toBe(true)
17
- expect(model.systems['archive']?.devOnly).toBe(true)
18
- })
19
-
20
- it('resolves authored sidebar navigation with Dashboard above Business and /clients as the client route', () => {
21
- const model = resolveOrganizationModel()
22
- const primaryEntries = Object.entries(model.navigation.sidebar.primary).sort(
23
- ([leftId, left], [rightId, right]) =>
24
- (left.order ?? Number.MAX_SAFE_INTEGER) - (right.order ?? Number.MAX_SAFE_INTEGER) ||
7
+ it('resolves the system defaults', () => {
8
+ const model = resolveOrganizationModel()
9
+
10
+ expect(model.version).toBe(1)
11
+ expect('surfaces' in model).toBe(false)
12
+ expect('navigationGroups' in model).toBe(false)
13
+ expect(model.systems).toEqual({})
14
+ expect(model.navigation.sidebar.primary).toEqual({})
15
+ expect(model.navigation.sidebar.bottom).toEqual({})
16
+ })
17
+
18
+ it('resolves authored sidebar navigation with explicit tenant systems', () => {
19
+ const model = resolveOrganizationModel({
20
+ navigation: {
21
+ sidebar: {
22
+ primary: {
23
+ dashboard: {
24
+ type: 'surface',
25
+ label: 'Dashboard',
26
+ path: '/',
27
+ surfaceType: 'dashboard',
28
+ order: 10,
29
+ targets: { systems: ['dashboard'] }
30
+ },
31
+ business: {
32
+ type: 'group',
33
+ label: 'Business',
34
+ order: 20,
35
+ children: {
36
+ clients: {
37
+ type: 'surface',
38
+ label: 'Clients',
39
+ path: '/clients',
40
+ surfaceType: 'list',
41
+ icon: 'clients',
42
+ order: 10,
43
+ targets: { systems: ['clients'] }
44
+ }
45
+ }
46
+ }
47
+ },
48
+ bottom: {}
49
+ }
50
+ },
51
+ systems: {
52
+ dashboard: {
53
+ id: 'dashboard',
54
+ order: 10,
55
+ label: 'Dashboard',
56
+ lifecycle: 'active'
57
+ },
58
+ clients: {
59
+ id: 'clients',
60
+ order: 20,
61
+ label: 'Clients',
62
+ lifecycle: 'active',
63
+ icon: 'clients'
64
+ }
65
+ }
66
+ })
67
+ const primaryEntries = Object.entries(model.navigation.sidebar.primary).sort(
68
+ ([leftId, left], [rightId, right]) =>
69
+ (left.order ?? Number.MAX_SAFE_INTEGER) - (right.order ?? Number.MAX_SAFE_INTEGER) ||
25
70
  leftId.localeCompare(rightId)
26
71
  )
27
72
 
@@ -29,8 +74,8 @@ describe('organization-model resolve', () => {
29
74
  expect(primaryEntries[1]?.[0]).toBe('business')
30
75
 
31
76
  const business = model.navigation.sidebar.primary.business
32
- expect(business?.type).toBe('group')
33
- if (business?.type === 'group') {
77
+ expect(business?.type).toBe('group')
78
+ if (business?.type === 'group') {
34
79
  expect(business.children.clients).toMatchObject({
35
80
  type: 'surface',
36
81
  path: '/clients',
@@ -117,20 +162,20 @@ describe('organization-model resolve', () => {
117
162
  })
118
163
 
119
164
  it('preserves sibling fields when overriding a nested property', () => {
120
- const model = resolveOrganizationModel({
121
- branding: {
122
- organizationName: 'OverriddenOrg'
123
- }
165
+ const model = resolveOrganizationModel({
166
+ branding: {
167
+ organizationName: 'OverriddenOrg'
168
+ }
124
169
  })
125
170
 
126
- expect(model.branding.organizationName).toBe('OverriddenOrg')
127
- expect(model.branding.productName).toBe('Elevasis')
128
- expect(Object.keys(model.systems).length).toBeGreaterThan(0)
129
- })
130
-
131
- it('overrides an existing system by id when the same key is supplied', () => {
132
- const model = resolveOrganizationModel({
133
- systems: {
171
+ expect(model.branding.organizationName).toBe('OverriddenOrg')
172
+ expect(model.branding.productName).toBe('Elevasis')
173
+ expect(model.systems).toEqual({})
174
+ })
175
+
176
+ it('overrides an existing system by id when the same key is supplied', () => {
177
+ const model = resolveOrganizationModel({
178
+ systems: {
134
179
  dashboard: {
135
180
  id: 'dashboard',
136
181
  order: 10,
@@ -283,15 +328,7 @@ describe('resolveOrganizationModelWithResources', () => {
283
328
  order: 910,
284
329
  label: 'CRM',
285
330
  enabled: true,
286
- config: { retryCount: 3 },
287
- content: {
288
- settings: {
289
- kind: 'config',
290
- type: 'kv',
291
- label: 'Settings',
292
- data: { entries: { retryCount: 1, fromContent: true } }
293
- }
294
- }
331
+ config: { retryCount: 3, fromConfig: true }
295
332
  }
296
333
  }
297
334
  }
@@ -317,6 +354,6 @@ describe('resolveOrganizationModelWithResources', () => {
317
354
  expect(resolved.systems.sys?.resources?.map((resource) => resource.id)).toEqual(['sys-workflow'])
318
355
  expect(resolved.systems['sys.crm']?.id).toBe('sys.crm')
319
356
  expect(resolved.systems['sys.crm']?.resources?.map((resource) => resource.id)).toEqual(['crm-workflow'])
320
- expect(resolved.systems['sys.crm']?.config).toEqual({ retryCount: 3, fromContent: true })
357
+ expect(resolved.systems['sys.crm']?.config).toEqual({ retryCount: 3, fromConfig: true })
321
358
  })
322
359
  })
@@ -313,21 +313,36 @@ describe('ontology contract validation', () => {
313
313
  ])
314
314
  })
315
315
 
316
- it('projects legacy entities, actions, and system content into compiled ontology indexes', () => {
316
+ it('projects legacy entities and actions while reading catalogs from authored ontology', () => {
317
317
  const model = OrganizationModelSchema.parse(
318
- makeMinimalModel({
319
- dashboard: {
320
- ...makeSystem('dashboard', '/'),
321
- content: {
322
- pipeline: {
323
- kind: 'schema',
324
- type: 'pipeline',
325
- label: 'Pipeline',
326
- data: { entityId: 'crm.deal' }
318
+ {
319
+ ...makeMinimalModel({
320
+ dashboard: {
321
+ ...makeSystem('dashboard', '/'),
322
+ ontology: {
323
+ catalogTypes: {
324
+ 'dashboard:catalog/pipeline': {
325
+ id: 'dashboard:catalog/pipeline',
326
+ kind: 'pipeline',
327
+ appliesTo: 'dashboard:object/crm.deal',
328
+ label: 'Pipeline',
329
+ entries: {
330
+ open: { label: 'Open', order: 10, semanticClass: 'open' }
331
+ }
332
+ }
333
+ }
327
334
  }
328
335
  }
336
+ }),
337
+ actions: {
338
+ send_reply: {
339
+ id: 'send_reply',
340
+ order: 10,
341
+ label: 'Send Reply',
342
+ affects: ['crm.deal']
343
+ }
329
344
  }
330
- })
345
+ }
331
346
  )
332
347
 
333
348
  const compilation = compileOrganizationOntology(model)
@@ -339,19 +354,27 @@ describe('ontology contract validation', () => {
339
354
  legacyId: 'crm.deal'
340
355
  })
341
356
  expect(compilation.ontology.actionTypes['dashboard:action/send_reply']?.legacyActionId).toBe('send_reply')
342
- expect(compilation.ontology.catalogTypes['dashboard:catalog/pipeline']).not.toHaveProperty('legacyContentId')
343
357
  expect(compilation.ontology.catalogTypes['dashboard:catalog/pipeline']?.origin).toMatchObject({
344
- source: 'legacy.system.content',
345
- legacyId: 'dashboard:pipeline'
358
+ source: 'system:dashboard.ontology',
359
+ systemPath: 'dashboard'
346
360
  })
347
361
  })
348
362
 
349
- it('adds origin metadata to authored and projected compiled ontology records without mutating source', () => {
363
+ it('adds origin metadata to authored compiled ontology records without mutating source', () => {
350
364
  const authoredObject = {
351
365
  id: 'dashboard:object/task',
352
366
  label: 'Task',
353
367
  ownerSystemId: 'dashboard'
354
368
  }
369
+ const authoredCatalog = {
370
+ id: 'dashboard:catalog/task-status',
371
+ label: 'Task Status',
372
+ kind: 'status-flow',
373
+ appliesTo: 'dashboard:object/task',
374
+ entries: {
375
+ planned: { label: 'Planned', order: 10 }
376
+ }
377
+ }
355
378
  const model = OrganizationModelSchema.parse({
356
379
  ...makeMinimalModel({
357
380
  dashboard: {
@@ -359,14 +382,9 @@ describe('ontology contract validation', () => {
359
382
  ontology: {
360
383
  objectTypes: {
361
384
  'dashboard:object/task': authoredObject
362
- }
363
- },
364
- content: {
365
- pipeline: {
366
- kind: 'schema',
367
- type: 'pipeline',
368
- label: 'Pipeline',
369
- data: { entityId: 'crm.deal' }
385
+ },
386
+ catalogTypes: {
387
+ 'dashboard:catalog/task-status': authoredCatalog
370
388
  }
371
389
  }
372
390
  }
@@ -380,13 +398,13 @@ describe('ontology contract validation', () => {
380
398
  source: 'system:dashboard.ontology',
381
399
  systemPath: 'dashboard'
382
400
  })
383
- expect(compilation.ontology.catalogTypes['dashboard:catalog/pipeline']?.origin).toMatchObject({
384
- kind: 'projected',
385
- source: 'legacy.system.content',
386
- systemPath: 'dashboard',
387
- legacyId: 'dashboard:pipeline'
401
+ expect(compilation.ontology.catalogTypes['dashboard:catalog/task-status']?.origin).toMatchObject({
402
+ kind: 'authored',
403
+ source: 'system:dashboard.ontology',
404
+ systemPath: 'dashboard'
388
405
  })
389
406
  expect(authoredObject).not.toHaveProperty('origin')
407
+ expect(authoredCatalog).not.toHaveProperty('origin')
390
408
  expect(model.systems.dashboard?.ontology?.objectTypes?.['dashboard:object/task']).not.toHaveProperty('origin')
391
409
  })
392
410
 
@@ -674,47 +692,31 @@ describe('system config contract', () => {
674
692
  expect(result.success).toBe(false)
675
693
  })
676
694
 
677
- it('projects bridge-era config:kv into effective resolved system config', () => {
695
+ it('resolves first-class system config without bridge-era config:kv content', () => {
678
696
  const model = OrganizationModelSchema.parse(
679
697
  makeMinimalModel({
680
698
  dashboard: {
681
699
  ...makeSystem('dashboard', '/'),
682
700
  config: {
683
701
  retries: 3,
684
- nested: { direct: true }
685
- },
686
- content: {
687
- settings: {
688
- kind: 'config',
689
- type: 'kv',
690
- label: 'Settings',
691
- data: { entries: { enabled: true, retries: 1, mode: 'bridge' } }
692
- }
702
+ mode: 'direct',
703
+ nested: { direct: true, enabled: true }
693
704
  }
694
705
  }
695
706
  })
696
707
  )
697
708
 
698
709
  expect(resolveSystemConfig(model, 'dashboard')).toEqual({
699
- enabled: true,
700
710
  retries: 3,
701
- mode: 'bridge',
702
- nested: { direct: true }
711
+ mode: 'direct',
712
+ nested: { direct: true, enabled: true }
703
713
  })
704
714
 
705
715
  const resolved = resolveOrganizationModelWithResources({
706
716
  systems: {
707
717
  dashboard: {
708
718
  ...makeSystem('dashboard', '/'),
709
- config: { retries: 3 },
710
- content: {
711
- settings: {
712
- kind: 'config',
713
- type: 'kv',
714
- label: 'Settings',
715
- data: { entries: { enabled: true, retries: 1 } }
716
- }
717
- }
719
+ config: { enabled: true, retries: 3 }
718
720
  }
719
721
  }
720
722
  })
@@ -780,8 +782,8 @@ describe('system action and policy validation', () => {
780
782
  sales: { id: 'sales', order: 10, label: 'Sales', enabled: true, lifecycle: 'active' },
781
783
  'sales.lead-gen': makeSystem('sales.lead-gen')
782
784
  }),
783
- systems: {
784
- sales: {
785
+ systems: {
786
+ sales: {
785
787
  id: 'sales',
786
788
  order: 10,
787
789
  label: 'Sales',
@@ -793,10 +795,17 @@ describe('system action and policy validation', () => {
793
795
  label: 'Lead Gen',
794
796
  lifecycle: 'active',
795
797
  actions: [{ actionId: 'lead-gen.company.source', intent: 'exposes' }],
796
- policies: ['policy.lead-gen.approval']
797
- }
798
- },
799
- policies: {
798
+ policies: ['policy.lead-gen.approval']
799
+ }
800
+ },
801
+ actions: {
802
+ 'lead-gen.company.source': {
803
+ id: 'lead-gen.company.source',
804
+ order: 10,
805
+ label: 'Source companies'
806
+ }
807
+ },
808
+ policies: {
800
809
  'policy.lead-gen.approval': {
801
810
  id: 'policy.lead-gen.approval',
802
811
  order: 10,
@@ -39,106 +39,3 @@ export interface LeadGenStageCatalogEntry {
39
39
  recordStageKey?: string
40
40
  }
41
41
 
42
- /**
43
- * Canonical lead-gen processing stage catalog.
44
- * Keys are the stage names written by workflow steps into processing_state jsonb.
45
- *
46
- * Ordered roughly by pipeline progression (prospecting -> outreach -> qualification).
47
- */
48
- export const LEAD_GEN_STAGE_CATALOG: Record<string, LeadGenStageCatalogEntry> = {
49
- // Prospecting - company population
50
- scraped: {
51
- key: 'scraped',
52
- label: 'Scraped',
53
- description: 'Company was scraped from a source directory (Apify actor run).',
54
- order: 1,
55
- entity: 'company'
56
- },
57
- populated: {
58
- key: 'populated',
59
- label: 'Companies found',
60
- description: 'Companies have been found and added to the lead-gen list.',
61
- order: 2,
62
- entity: 'company'
63
- },
64
- crawled: {
65
- key: 'crawled',
66
- label: 'Websites crawled',
67
- description:
68
- 'Company websites have been crawled (e.g. via Apify) and raw page content stored for downstream LLM analysis.',
69
- order: 2.5,
70
- entity: 'company'
71
- },
72
- extracted: {
73
- key: 'extracted',
74
- label: 'Websites analyzed',
75
- description: 'Company websites have been analyzed for business signals.',
76
- order: 3,
77
- entity: 'company'
78
- },
79
- enriched: {
80
- key: 'enriched',
81
- label: 'Enriched',
82
- description: 'Company or contact enriched with third-party data (e.g. Tomba, Anymailfinder).',
83
- order: 4,
84
- entity: 'company'
85
- },
86
- 'decision-makers-enriched': {
87
- key: 'decision-makers-enriched',
88
- label: 'Decision-makers found',
89
- description: 'Decision-maker contacts discovered and attached to a qualified company.',
90
- order: 6,
91
- entity: 'company',
92
- recordEntity: 'contact',
93
- recordStageKey: 'discovered'
94
- },
95
-
96
- // Prospecting - contact discovery
97
- discovered: {
98
- key: 'discovered',
99
- label: 'Decision-makers found',
100
- description: 'Decision-maker contact details have been found.',
101
- order: 5,
102
- entity: 'contact'
103
- },
104
- verified: {
105
- key: 'verified',
106
- label: 'Emails verified',
107
- description: 'Contact email addresses have been checked for deliverability.',
108
- order: 7,
109
- entity: 'contact'
110
- },
111
-
112
- // Qualification
113
- qualified: {
114
- key: 'qualified',
115
- label: 'Companies qualified',
116
- description: 'Companies have been scored against the qualification criteria.',
117
- order: 8,
118
- entity: 'company'
119
- },
120
-
121
- // Outreach
122
- personalized: {
123
- key: 'personalized',
124
- label: 'Personalized',
125
- description: 'Outreach message personalized for the contact (Instantly personalization workflow).',
126
- order: 9,
127
- entity: 'contact'
128
- },
129
- uploaded: {
130
- key: 'uploaded',
131
- label: 'Reviewed and exported',
132
- description: 'Approved records have been reviewed and exported for handoff.',
133
- order: 10,
134
- entity: 'company',
135
- additionalEntities: ['contact']
136
- },
137
- interested: {
138
- key: 'interested',
139
- label: 'Interested',
140
- description: 'Contact replied with a positive signal (Instantly reply-handler transition).',
141
- order: 11,
142
- entity: 'contact'
143
- }
144
- }