@elevasis/core 0.24.0 → 0.25.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 (50) hide show
  1. package/dist/index.d.ts +3117 -2166
  2. package/dist/index.js +574 -16
  3. package/dist/knowledge/index.d.ts +122 -7
  4. package/dist/organization-model/index.d.ts +3117 -2166
  5. package/dist/organization-model/index.js +574 -16
  6. package/dist/test-utils/index.d.ts +135 -45
  7. package/dist/test-utils/index.js +122 -14
  8. package/package.json +3 -3
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +139 -101
  10. package/src/execution/engine/llm/adapters/__tests__/openrouter.integration.test.ts +10 -10
  11. package/src/execution/engine/workflow/types.ts +5 -7
  12. package/src/knowledge/__tests__/queries.test.ts +960 -546
  13. package/src/knowledge/format.ts +322 -100
  14. package/src/knowledge/index.ts +18 -5
  15. package/src/knowledge/queries.ts +1004 -239
  16. package/src/organization-model/__tests__/deprecate-helpers.test.ts +71 -0
  17. package/src/organization-model/__tests__/domains/resources.test.ts +19 -8
  18. package/src/organization-model/__tests__/domains/topology.test.ts +188 -0
  19. package/src/organization-model/__tests__/graph.test.ts +98 -7
  20. package/src/organization-model/__tests__/resolve.test.ts +9 -7
  21. package/src/organization-model/__tests__/scaffolders.test.ts +93 -0
  22. package/src/organization-model/__tests__/schema.test.ts +14 -4
  23. package/src/organization-model/defaults.ts +5 -3
  24. package/src/organization-model/domains/resources.ts +63 -20
  25. package/src/organization-model/domains/topology.ts +261 -0
  26. package/src/organization-model/graph/build.ts +63 -15
  27. package/src/organization-model/graph/schema.ts +4 -3
  28. package/src/organization-model/graph/types.ts +5 -4
  29. package/src/organization-model/helpers.ts +76 -9
  30. package/src/organization-model/icons.ts +1 -0
  31. package/src/organization-model/index.ts +7 -5
  32. package/src/organization-model/ontology.ts +2 -5
  33. package/src/organization-model/organization-model.mdx +16 -11
  34. package/src/organization-model/published.ts +51 -15
  35. package/src/organization-model/scaffolders/helpers.ts +84 -0
  36. package/src/organization-model/scaffolders/index.ts +19 -0
  37. package/src/organization-model/scaffolders/scaffoldKnowledgeNode.ts +48 -0
  38. package/src/organization-model/scaffolders/scaffoldOntologyRecord.ts +38 -0
  39. package/src/organization-model/scaffolders/scaffoldResource.ts +59 -0
  40. package/src/organization-model/scaffolders/scaffoldSystem.ts +110 -0
  41. package/src/organization-model/scaffolders/types.ts +81 -0
  42. package/src/organization-model/schema.ts +51 -11
  43. package/src/organization-model/types.ts +25 -11
  44. package/src/platform/constants/versions.ts +1 -1
  45. package/src/platform/registry/__tests__/validation.test.ts +199 -14
  46. package/src/platform/registry/resource-registry.ts +11 -11
  47. package/src/platform/registry/validation.ts +226 -34
  48. package/src/reference/_generated/contracts.md +139 -101
  49. package/src/reference/glossary.md +74 -72
  50. package/src/supabase/database.types.ts +3156 -3153
@@ -0,0 +1,71 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ getSystemDeprecationDependents,
4
+ type OrganizationModel,
5
+ type OmTopologyDomain
6
+ } from '../index'
7
+
8
+ const model = {
9
+ systems: {
10
+ sales: {
11
+ id: 'sales',
12
+ order: 1,
13
+ label: 'Sales',
14
+ lifecycle: 'active',
15
+ systems: {
16
+ crm: {
17
+ id: 'sales.crm',
18
+ order: 2,
19
+ label: 'CRM',
20
+ lifecycle: 'active'
21
+ }
22
+ }
23
+ }
24
+ },
25
+ resources: {
26
+ 'crm-active-workflow': {
27
+ id: 'crm-active-workflow',
28
+ order: 1,
29
+ kind: 'workflow',
30
+ systemPath: 'sales.crm',
31
+ status: 'active',
32
+ codeRefs: []
33
+ },
34
+ 'crm-archived-workflow': {
35
+ id: 'crm-archived-workflow',
36
+ order: 2,
37
+ kind: 'workflow',
38
+ systemPath: 'sales.crm',
39
+ status: 'archived',
40
+ codeRefs: []
41
+ }
42
+ },
43
+ knowledge: {},
44
+ topology: {
45
+ version: 1,
46
+ relationships: {
47
+ 'crm-active-uses-sales': {
48
+ from: { kind: 'resource', id: 'crm-active-workflow' },
49
+ kind: 'uses',
50
+ to: { kind: 'system', id: 'sales' }
51
+ },
52
+ 'archived-resource-edge': {
53
+ from: { kind: 'resource', id: 'crm-archived-workflow' },
54
+ kind: 'uses',
55
+ to: { kind: 'system', id: 'sales' }
56
+ }
57
+ }
58
+ } satisfies OmTopologyDomain
59
+ } as OrganizationModel
60
+
61
+ describe('getSystemDeprecationDependents', () => {
62
+ it('enumerates active resources and topology edges for a system tree', () => {
63
+ const dependents = getSystemDeprecationDependents(model, 'sales', { includeDescendants: true })
64
+
65
+ expect(dependents.resources.map((resource) => resource.id)).toEqual(['crm-active-workflow'])
66
+ expect(dependents.topologyEdges.map((edge) => edge.id).sort()).toEqual([
67
+ 'archived-resource-edge',
68
+ 'crm-active-uses-sales'
69
+ ])
70
+ })
71
+ })
@@ -31,12 +31,13 @@ const VALID_ROLE = {
31
31
  const WORKFLOW_RESOURCE = {
32
32
  id: 'LGN-01-company-scrape',
33
33
  order: 10,
34
- kind: 'workflow' as const,
35
- systemPath: 'sys.lead-gen',
36
- ownerRoleId: 'role.sales-ops',
37
- status: 'active' as const,
38
- actionKey: 'lead-gen.company.scrape',
39
- codeRefs: [
34
+ kind: 'workflow' as const,
35
+ systemPath: 'sys.lead-gen',
36
+ title: 'Company Scrape',
37
+ description: 'Scrapes company data for lead generation.',
38
+ ownerRoleId: 'role.sales-ops',
39
+ status: 'active' as const,
40
+ codeRefs: [
40
41
  {
41
42
  path: 'operations/src/lead-gen/company-scrape/index.ts',
42
43
  role: 'entrypoint' as const,
@@ -231,7 +232,8 @@ describe('ResourceEntrySchema', () => {
231
232
 
232
233
  it('accepts optional ontology bindings on resource variants without changing top-level event emissions', () => {
233
234
  const ontology = {
234
- implements: ['sys.lead-gen:action/company.scrape'],
235
+ actions: ['sys.lead-gen:action/company.scrape'],
236
+ primaryAction: 'sys.lead-gen:action/company.scrape',
235
237
  reads: ['sys.lead-gen:object/company'],
236
238
  writes: ['sys.lead-gen:object/company'],
237
239
  usesCatalogs: ['sys.lead-gen:catalog/build-template'],
@@ -268,7 +270,16 @@ describe('ResourceEntrySchema', () => {
268
270
  it('rejects malformed ontology binding ids', () => {
269
271
  expect(
270
272
  ResourceOntologyBindingSchema.safeParse({
271
- implements: ['sys.lead-gen/action/company.scrape']
273
+ actions: ['sys.lead-gen/action/company.scrape']
274
+ }).success
275
+ ).toBe(false)
276
+ })
277
+
278
+ it('rejects primaryAction values outside actions', () => {
279
+ expect(
280
+ ResourceOntologyBindingSchema.safeParse({
281
+ actions: ['sys.lead-gen:action/company.scrape'],
282
+ primaryAction: 'sys.lead-gen:action/company.qualify'
272
283
  }).success
273
284
  ).toBe(false)
274
285
  })
@@ -0,0 +1,188 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { ZodError } from 'zod'
3
+ import {
4
+ OmTopologyDomainSchema,
5
+ OmTopologyMetadataSchema,
6
+ defineTopology,
7
+ defineTopologyRelationship,
8
+ parseTopologyNodeRef,
9
+ topologyRef
10
+ } from '../../domains/topology'
11
+ import { resolveOrganizationModel } from '../../resolve'
12
+
13
+ const VALID_SYSTEM = {
14
+ id: 'sales',
15
+ order: 10,
16
+ label: 'Sales',
17
+ enabled: true,
18
+ lifecycle: 'active' as const
19
+ }
20
+
21
+ const SOURCE_RESOURCE = {
22
+ id: 'email-discovery',
23
+ order: 10,
24
+ kind: 'workflow' as const,
25
+ systemPath: 'sales',
26
+ status: 'active' as const
27
+ }
28
+
29
+ const TARGET_RESOURCE = {
30
+ id: 'email-verification',
31
+ order: 20,
32
+ kind: 'workflow' as const,
33
+ systemPath: 'sales',
34
+ status: 'active' as const
35
+ }
36
+
37
+ describe('OM topology domain', () => {
38
+ it('accepts first-class topology relationships', () => {
39
+ const model = resolveOrganizationModel({
40
+ systems: { sales: VALID_SYSTEM },
41
+ resources: {
42
+ 'email-discovery': SOURCE_RESOURCE,
43
+ 'email-verification': TARGET_RESOURCE
44
+ },
45
+ topology: {
46
+ version: 1,
47
+ relationships: {
48
+ 'email-discovery-triggers-email-verification': {
49
+ from: { kind: 'resource', id: 'email-discovery' },
50
+ kind: 'triggers',
51
+ to: { kind: 'resource', id: 'email-verification' },
52
+ systemPath: 'sales',
53
+ required: true,
54
+ metadata: { source: 'authored' }
55
+ }
56
+ }
57
+ }
58
+ })
59
+
60
+ expect(model.topology.relationships['email-discovery-triggers-email-verification']).toMatchObject({
61
+ from: { kind: 'resource', id: 'email-discovery' },
62
+ kind: 'triggers',
63
+ to: { kind: 'resource', id: 'email-verification' },
64
+ required: true
65
+ })
66
+ })
67
+
68
+ it('rejects dangling first-class topology refs', () => {
69
+ expect(() =>
70
+ resolveOrganizationModel({
71
+ systems: { sales: VALID_SYSTEM },
72
+ resources: { 'email-discovery': SOURCE_RESOURCE },
73
+ topology: {
74
+ version: 1,
75
+ relationships: {
76
+ 'email-discovery-triggers-missing': {
77
+ from: { kind: 'resource', id: 'email-discovery' },
78
+ kind: 'triggers',
79
+ to: { kind: 'resource', id: 'missing-resource' }
80
+ }
81
+ }
82
+ }
83
+ })
84
+ ).toThrow(/unknown resource \\"missing-resource\\"/)
85
+ })
86
+
87
+ it('reports dangling topology refs on the relationship side path', () => {
88
+ let error: unknown
89
+
90
+ try {
91
+ resolveOrganizationModel({
92
+ systems: { sales: VALID_SYSTEM },
93
+ resources: { 'email-discovery': SOURCE_RESOURCE },
94
+ topology: {
95
+ version: 1,
96
+ relationships: {
97
+ 'email-discovery-triggers-missing': {
98
+ from: { kind: 'resource', id: 'email-discovery' },
99
+ kind: 'triggers',
100
+ to: { kind: 'resource', id: 'missing-resource' }
101
+ }
102
+ }
103
+ }
104
+ })
105
+ } catch (caught) {
106
+ error = caught
107
+ }
108
+
109
+ expect(error).toBeInstanceOf(ZodError)
110
+ if (!(error instanceof ZodError)) {
111
+ throw new Error('Expected resolveOrganizationModel to throw a ZodError')
112
+ }
113
+ expect(error.issues).toEqual(
114
+ expect.arrayContaining([
115
+ expect.objectContaining({
116
+ path: ['topology', 'relationships', 'email-discovery-triggers-missing', 'to']
117
+ })
118
+ ])
119
+ )
120
+ })
121
+
122
+ it('rejects secret-like topology metadata', () => {
123
+ expect(
124
+ OmTopologyMetadataSchema.safeParse({
125
+ owner: 'sales-ops',
126
+ credentials: { apiKey: 'sk-test-12345678901234567890' }
127
+ }).success
128
+ ).toBe(false)
129
+ })
130
+
131
+ it('compiles typed resource objects through authoring helpers', () => {
132
+ const relationship = defineTopologyRelationship({
133
+ from: SOURCE_RESOURCE,
134
+ kind: 'uses',
135
+ to: topologyRef.externalResource('apollo'),
136
+ required: true
137
+ })
138
+
139
+ expect(relationship).toEqual({
140
+ from: { kind: 'resource', id: 'email-discovery' },
141
+ kind: 'uses',
142
+ to: { kind: 'externalResource', id: 'apollo' },
143
+ required: true
144
+ })
145
+
146
+ expect(
147
+ defineTopology({
148
+ 'email-discovery-uses-apollo': {
149
+ from: SOURCE_RESOURCE,
150
+ kind: 'uses',
151
+ to: topologyRef.externalResource('apollo')
152
+ }
153
+ })
154
+ ).toMatchObject({
155
+ version: 1,
156
+ relationships: {
157
+ 'email-discovery-uses-apollo': {
158
+ from: { kind: 'resource', id: 'email-discovery' },
159
+ to: { kind: 'externalResource', id: 'apollo' }
160
+ }
161
+ }
162
+ })
163
+ })
164
+
165
+ it('parses boundary string refs without making raw strings canonical', () => {
166
+ expect(parseTopologyNodeRef('resource:email-discovery')).toEqual({
167
+ kind: 'resource',
168
+ id: 'email-discovery'
169
+ })
170
+ expect(parseTopologyNodeRef('ontology:sales:action/contact.verify-email')).toEqual({
171
+ kind: 'ontology',
172
+ id: 'sales:action/contact.verify-email'
173
+ })
174
+ expect(() => parseTopologyNodeRef('email-discovery')).toThrow(/must use <kind>:<id>/)
175
+ expect(() =>
176
+ OmTopologyDomainSchema.parse({
177
+ version: 1,
178
+ relationships: {
179
+ invalid: {
180
+ from: 'resource:email-discovery',
181
+ kind: 'uses',
182
+ to: { kind: 'resource', id: 'email-verification' }
183
+ }
184
+ }
185
+ })
186
+ ).toThrow()
187
+ })
188
+ })
@@ -115,15 +115,16 @@ describe('organization graph', () => {
115
115
  expect(() => OrganizationGraphEdgeKindSchema.parse('links')).not.toThrow()
116
116
  expect(() => OrganizationGraphEdgeKindSchema.parse('affects')).not.toThrow()
117
117
  expect(() => OrganizationGraphEdgeKindSchema.parse('emits')).not.toThrow()
118
- expect(() => OrganizationGraphEdgeKindSchema.parse('originates_from')).not.toThrow()
119
- expect(() => OrganizationGraphEdgeKindSchema.parse('triggers')).not.toThrow()
118
+ expect(() => OrganizationGraphEdgeKindSchema.parse('originates_from')).not.toThrow()
119
+ expect(() => OrganizationGraphEdgeKindSchema.parse('triggers')).not.toThrow()
120
+ expect(() => OrganizationGraphEdgeKindSchema.parse('approval')).not.toThrow()
120
121
  expect(() => OrganizationGraphNodeKindSchema.parse('surface')).not.toThrow()
121
122
  expect(() => OrganizationGraphNodeKindSchema.parse('customer-segment')).not.toThrow()
122
123
  expect(() => OrganizationGraphNodeKindSchema.parse('offering')).not.toThrow()
123
124
  expect(() => OrganizationGraphNodeKindSchema.parse('goal')).not.toThrow()
124
125
  expect(() => OrganizationGraphNodeKindSchema.parse('navigation-group')).not.toThrow()
125
126
  expect(() => OrganizationGraphNodeKindSchema.parse('ontology')).not.toThrow()
126
- expect(() => OrganizationGraphEdgeKindSchema.parse('implements')).not.toThrow()
127
+ expect(() => OrganizationGraphEdgeKindSchema.parse('actions')).not.toThrow()
127
128
  expect(() => OrganizationGraphEdgeKindSchema.parse('reads')).not.toThrow()
128
129
  expect(() => OrganizationGraphEdgeKindSchema.parse('writes')).not.toThrow()
129
130
  expect(() => OrganizationGraphEdgeKindSchema.parse('uses_catalog')).not.toThrow()
@@ -431,7 +432,8 @@ describe('organization graph', () => {
431
432
  systemPath: 'test.bindings',
432
433
  status: 'active',
433
434
  ontology: {
434
- implements: ['test.bindings:action/update-deal'],
435
+ actions: ['test.bindings:action/update-deal'],
436
+ primaryAction: 'test.bindings:action/update-deal',
435
437
  reads: ['test.bindings:object/deal'],
436
438
  writes: ['test.bindings:object/deal'],
437
439
  usesCatalogs: ['test.bindings:catalog/pipeline'],
@@ -445,7 +447,7 @@ describe('organization graph', () => {
445
447
  expect(
446
448
  graph.edges.find(
447
449
  (edge) =>
448
- edge.kind === 'implements' &&
450
+ edge.kind === 'actions' &&
449
451
  edge.sourceId === 'resource:deal-workflow' &&
450
452
  edge.targetId === 'ontology:test.bindings:action/update-deal'
451
453
  )
@@ -483,8 +485,97 @@ describe('organization graph', () => {
483
485
  )
484
486
  ).toBeDefined()
485
487
  })
486
-
487
- it('projects surface and navigation-group nodes from recursive sidebar leaves', () => {
488
+
489
+ it('projects topology relationships without collapsing ontology binding edges', () => {
490
+ const model = resolveOrganizationModel({
491
+ systems: {
492
+ 'test.topology': {
493
+ id: 'test.topology',
494
+ order: 10,
495
+ label: 'Topology System',
496
+ enabled: true,
497
+ ontology: {
498
+ actionTypes: {
499
+ 'test.topology:action/discover-email': {
500
+ id: 'test.topology:action/discover-email',
501
+ label: 'Discover Email'
502
+ }
503
+ }
504
+ }
505
+ }
506
+ },
507
+ resources: {
508
+ 'email-discovery': {
509
+ id: 'email-discovery',
510
+ order: 10,
511
+ kind: 'workflow',
512
+ systemPath: 'test.topology',
513
+ status: 'active',
514
+ ontology: {
515
+ actions: ['test.topology:action/discover-email'],
516
+ primaryAction: 'test.topology:action/discover-email'
517
+ }
518
+ },
519
+ 'email-verification': {
520
+ id: 'email-verification',
521
+ order: 20,
522
+ kind: 'workflow',
523
+ systemPath: 'test.topology',
524
+ status: 'active'
525
+ }
526
+ },
527
+ topology: {
528
+ version: 1,
529
+ relationships: {
530
+ 'email-discovery-triggers-email-verification': {
531
+ from: { kind: 'resource', id: 'email-discovery' },
532
+ kind: 'triggers',
533
+ to: { kind: 'resource', id: 'email-verification' },
534
+ required: true
535
+ },
536
+ 'email-verification-approval-human-review': {
537
+ from: { kind: 'resource', id: 'email-verification' },
538
+ kind: 'approval',
539
+ to: { kind: 'humanCheckpoint', id: 'email-review' }
540
+ }
541
+ }
542
+ }
543
+ })
544
+ const graph = buildOrganizationGraph({ organizationModel: model })
545
+
546
+ expect(
547
+ graph.edges.find(
548
+ (edge) =>
549
+ edge.kind === 'actions' &&
550
+ edge.sourceId === 'resource:email-discovery' &&
551
+ edge.targetId === 'ontology:test.topology:action/discover-email'
552
+ )
553
+ ).toBeDefined()
554
+ expect(
555
+ graph.edges.find(
556
+ (edge) =>
557
+ edge.kind === 'triggers' &&
558
+ edge.relationshipType === 'triggers' &&
559
+ edge.sourceId === 'resource:email-discovery' &&
560
+ edge.targetId === 'resource:email-verification'
561
+ )
562
+ ).toBeDefined()
563
+ expect(graph.nodes.find((node) => node.id === 'resource:email-review')).toMatchObject({
564
+ kind: 'resource',
565
+ resourceType: 'human_checkpoint'
566
+ })
567
+ expect(
568
+ graph.edges.find(
569
+ (edge) =>
570
+ edge.kind === 'approval' &&
571
+ edge.relationshipType === 'approval' &&
572
+ edge.sourceId === 'resource:email-verification' &&
573
+ edge.targetId === 'resource:email-review'
574
+ )
575
+ ).toBeDefined()
576
+ })
577
+
578
+ it('projects surface and navigation-group nodes from recursive sidebar leaves', () => {
488
579
  const model = resolveOrganizationModel({
489
580
  navigation: {
490
581
  sidebar: {
@@ -31,13 +31,15 @@ describe('organization-model resolve', () => {
31
31
  const business = model.navigation.sidebar.primary.business
32
32
  expect(business?.type).toBe('group')
33
33
  if (business?.type === 'group') {
34
- expect(business.children.clients).toMatchObject({
35
- type: 'surface',
36
- path: '/clients',
37
- targets: { systems: ['clients'] }
38
- })
39
- }
40
- })
34
+ expect(business.children.clients).toMatchObject({
35
+ type: 'surface',
36
+ path: '/clients',
37
+ icon: 'clients',
38
+ targets: { systems: ['clients'] }
39
+ })
40
+ }
41
+ expect(model.systems.clients?.icon).toBe('clients')
42
+ })
41
43
 
42
44
  it('adds tenant systems additively to the merged record', () => {
43
45
  const baseSize = Object.keys(resolveOrganizationModel().systems).length
@@ -0,0 +1,93 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { DEFAULT_ORGANIZATION_MODEL, scaffoldOrganizationModel, type OrganizationModel } from '..'
3
+
4
+ const BASE_MODEL: OrganizationModel = {
5
+ ...DEFAULT_ORGANIZATION_MODEL,
6
+ systems: {
7
+ sales: {
8
+ id: 'sales',
9
+ order: 10,
10
+ label: 'Sales',
11
+ lifecycle: 'active',
12
+ systems: {
13
+ crm: {
14
+ id: 'sales.crm',
15
+ order: 20,
16
+ label: 'CRM',
17
+ lifecycle: 'active'
18
+ }
19
+ }
20
+ }
21
+ },
22
+ resources: {},
23
+ knowledge: {}
24
+ }
25
+
26
+ describe('organization-model scaffolders', () => {
27
+ it('plans the minimal system scaffold without route files', () => {
28
+ const plan = scaffoldOrganizationModel(BASE_MODEL, {
29
+ intent: 'system',
30
+ id: 'sales.partners',
31
+ label: 'Partners',
32
+ description: 'Partner operations.',
33
+ noProject: true
34
+ })
35
+
36
+ expect(plan.intent).toBe('system')
37
+ expect(plan.id).toBe('sales.partners')
38
+ expect(plan.edits.map((edit) => edit.path)).toEqual(
39
+ expect.arrayContaining([
40
+ 'packages/elevasis-core/src/organization-model/systems.ts',
41
+ 'packages/elevasis-core/src/organization-model/navigation.ts',
42
+ 'packages/elevasis-core/src/organization-model/roles.ts',
43
+ 'packages/elevasis-core/src/organization-model/assembly.ts',
44
+ 'packages/elevasis-core/src/organization-model/knowledge.ts'
45
+ ])
46
+ )
47
+ expect(plan.writes.some((write) => write.path.endsWith('manifest.stub.ts'))).toBe(true)
48
+ expect(plan.writes.some((write) => write.path.includes('/routes/'))).toBe(false)
49
+ expect(plan.projectCommand).toBeUndefined()
50
+ })
51
+
52
+ it('defaults system scaffolds to a project chain unless skipped', () => {
53
+ const plan = scaffoldOrganizationModel(BASE_MODEL, {
54
+ intent: 'system',
55
+ id: 'sales.enablement',
56
+ label: 'Enablement'
57
+ })
58
+
59
+ expect(plan.projectCommand).toContain('project:create --link-om sales.enablement')
60
+ })
61
+
62
+ it('keeps ontology/resource/knowledge project chains opt-in', () => {
63
+ const ontology = scaffoldOrganizationModel(BASE_MODEL, {
64
+ intent: 'ontology',
65
+ id: 'deal-score',
66
+ systemPath: 'sales.crm',
67
+ kind: 'object'
68
+ })
69
+ const resource = scaffoldOrganizationModel(BASE_MODEL, {
70
+ intent: 'resource',
71
+ id: 'crm-score-workflow',
72
+ systemPath: 'sales.crm'
73
+ })
74
+ const knowledge = scaffoldOrganizationModel(BASE_MODEL, {
75
+ intent: 'knowledge',
76
+ id: 'knowledge.crm-scoring',
77
+ systemPath: 'sales.crm'
78
+ })
79
+
80
+ expect(ontology.projectCommand).toBeUndefined()
81
+ expect(resource.projectCommand).toBeUndefined()
82
+ expect(knowledge.projectCommand).toBeUndefined()
83
+ })
84
+
85
+ it('rejects duplicate system paths during planning', () => {
86
+ expect(() =>
87
+ scaffoldOrganizationModel(BASE_MODEL, {
88
+ intent: 'system',
89
+ id: 'sales.crm'
90
+ })
91
+ ).toThrow('system already exists: sales.crm')
92
+ })
93
+ })
@@ -333,9 +333,17 @@ describe('ontology contract validation', () => {
333
333
  const compilation = compileOrganizationOntology(model)
334
334
 
335
335
  expect(compilation.diagnostics).toEqual([])
336
- expect(compilation.ontology.objectTypes['dashboard:object/crm.deal']?.legacyEntityId).toBe('crm.deal')
336
+ expect(compilation.ontology.objectTypes['dashboard:object/crm.deal']).not.toHaveProperty('legacyEntityId')
337
+ expect(compilation.ontology.objectTypes['dashboard:object/crm.deal']?.origin).toMatchObject({
338
+ source: 'legacy.entities',
339
+ legacyId: 'crm.deal'
340
+ })
337
341
  expect(compilation.ontology.actionTypes['dashboard:action/send_reply']?.legacyActionId).toBe('send_reply')
338
- expect(compilation.ontology.catalogTypes['dashboard:catalog/pipeline']?.legacyContentId).toBe('dashboard:pipeline')
342
+ expect(compilation.ontology.catalogTypes['dashboard:catalog/pipeline']).not.toHaveProperty('legacyContentId')
343
+ expect(compilation.ontology.catalogTypes['dashboard:catalog/pipeline']?.origin).toMatchObject({
344
+ source: 'legacy.system.content',
345
+ legacyId: 'dashboard:pipeline'
346
+ })
339
347
  })
340
348
 
341
349
  it('adds origin metadata to authored and projected compiled ontology records without mutating source', () => {
@@ -477,7 +485,8 @@ describe('ontology contract validation', () => {
477
485
  systemPath: 'dashboard',
478
486
  status: 'active',
479
487
  ontology: {
480
- implements: ['dashboard:action/process-task'],
488
+ actions: ['dashboard:action/process-task'],
489
+ primaryAction: 'dashboard:action/process-task',
481
490
  reads: ['dashboard:object/task'],
482
491
  writes: ['dashboard:object/task'],
483
492
  usesCatalogs: ['dashboard:catalog/task-status'],
@@ -605,7 +614,8 @@ describe('ontology contract validation', () => {
605
614
  systemPath: 'dashboard',
606
615
  status: 'active',
607
616
  ontology: {
608
- implements: ['dashboard:action/missing-task'],
617
+ actions: ['dashboard:action/missing-task'],
618
+ primaryAction: 'dashboard:action/missing-task',
609
619
  reads: ['dashboard:object/missing-task'],
610
620
  usesCatalogs: ['dashboard:catalog/missing-status'],
611
621
  emits: ['dashboard:event/missing-event']
@@ -17,6 +17,7 @@ import { DEFAULT_ORGANIZATION_MODEL_OFFERINGS } from './domains/offerings'
17
17
  import { DEFAULT_ORGANIZATION_MODEL_ROLES } from './domains/roles'
18
18
  import { DEFAULT_ORGANIZATION_MODEL_GOALS } from './domains/goals'
19
19
  import { DEFAULT_ORGANIZATION_MODEL_RESOURCES } from './domains/resources'
20
+ import { DEFAULT_ORGANIZATION_MODEL_TOPOLOGY } from './domains/topology'
20
21
  import { CRM_ACTION_ENTRIES, DEFAULT_ORGANIZATION_MODEL_ACTIONS, LEAD_GEN_ACTION_ENTRIES } from './domains/actions'
21
22
  import { DEFAULT_ORGANIZATION_MODEL_ENTITIES as DEFAULT_ENTITIES } from './domains/entities'
22
23
  import { DEFAULT_ORGANIZATION_MODEL_POLICIES } from './domains/policies'
@@ -45,7 +46,7 @@ const DEFAULT_ORGANIZATION_MODEL_NAVIGATION: OrganizationModelNavigation = {
45
46
  business: {
46
47
  type: 'group',
47
48
  label: 'Business',
48
- icon: 'business',
49
+ icon: 'briefcase',
49
50
  order: 20,
50
51
  children: {
51
52
  sales: {
@@ -62,7 +63,7 @@ const DEFAULT_ORGANIZATION_MODEL_NAVIGATION: OrganizationModelNavigation = {
62
63
  label: 'Clients',
63
64
  path: '/clients',
64
65
  surfaceType: 'list',
65
- icon: 'projects',
66
+ icon: 'clients',
66
67
  order: 20,
67
68
  targets: { systems: ['clients'] }
68
69
  },
@@ -437,7 +438,7 @@ export const DEFAULT_ORGANIZATION_MODEL: OrganizationModel = {
437
438
  enabled: true,
438
439
  lifecycle: 'active',
439
440
  color: 'orange',
440
- icon: 'projects',
441
+ icon: 'clients',
441
442
  path: '/clients'
442
443
  },
443
444
  operations: {
@@ -741,6 +742,7 @@ export const DEFAULT_ORGANIZATION_MODEL: OrganizationModel = {
741
742
  },
742
743
  ontology: DEFAULT_ONTOLOGY_SCOPE,
743
744
  resources: DEFAULT_ORGANIZATION_MODEL_RESOURCES,
745
+ topology: DEFAULT_ORGANIZATION_MODEL_TOPOLOGY,
744
746
  actions: DEFAULT_ORGANIZATION_MODEL_ACTIONS,
745
747
  entities: DEFAULT_ORGANIZATION_MODEL_ENTITIES,
746
748
  policies: DEFAULT_ORGANIZATION_MODEL_POLICIES,