@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.
- package/dist/index.d.ts +3117 -2166
- package/dist/index.js +574 -16
- package/dist/knowledge/index.d.ts +122 -7
- package/dist/organization-model/index.d.ts +3117 -2166
- package/dist/organization-model/index.js +574 -16
- package/dist/test-utils/index.d.ts +135 -45
- package/dist/test-utils/index.js +122 -14
- package/package.json +3 -3
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +139 -101
- package/src/execution/engine/llm/adapters/__tests__/openrouter.integration.test.ts +10 -10
- package/src/execution/engine/workflow/types.ts +5 -7
- package/src/knowledge/__tests__/queries.test.ts +960 -546
- package/src/knowledge/format.ts +322 -100
- package/src/knowledge/index.ts +18 -5
- package/src/knowledge/queries.ts +1004 -239
- package/src/organization-model/__tests__/deprecate-helpers.test.ts +71 -0
- package/src/organization-model/__tests__/domains/resources.test.ts +19 -8
- package/src/organization-model/__tests__/domains/topology.test.ts +188 -0
- package/src/organization-model/__tests__/graph.test.ts +98 -7
- package/src/organization-model/__tests__/resolve.test.ts +9 -7
- package/src/organization-model/__tests__/scaffolders.test.ts +93 -0
- package/src/organization-model/__tests__/schema.test.ts +14 -4
- package/src/organization-model/defaults.ts +5 -3
- package/src/organization-model/domains/resources.ts +63 -20
- package/src/organization-model/domains/topology.ts +261 -0
- package/src/organization-model/graph/build.ts +63 -15
- package/src/organization-model/graph/schema.ts +4 -3
- package/src/organization-model/graph/types.ts +5 -4
- package/src/organization-model/helpers.ts +76 -9
- package/src/organization-model/icons.ts +1 -0
- package/src/organization-model/index.ts +7 -5
- package/src/organization-model/ontology.ts +2 -5
- package/src/organization-model/organization-model.mdx +16 -11
- package/src/organization-model/published.ts +51 -15
- package/src/organization-model/scaffolders/helpers.ts +84 -0
- package/src/organization-model/scaffolders/index.ts +19 -0
- package/src/organization-model/scaffolders/scaffoldKnowledgeNode.ts +48 -0
- package/src/organization-model/scaffolders/scaffoldOntologyRecord.ts +38 -0
- package/src/organization-model/scaffolders/scaffoldResource.ts +59 -0
- package/src/organization-model/scaffolders/scaffoldSystem.ts +110 -0
- package/src/organization-model/scaffolders/types.ts +81 -0
- package/src/organization-model/schema.ts +51 -11
- package/src/organization-model/types.ts +25 -11
- package/src/platform/constants/versions.ts +1 -1
- package/src/platform/registry/__tests__/validation.test.ts +199 -14
- package/src/platform/registry/resource-registry.ts +11 -11
- package/src/platform/registry/validation.ts +226 -34
- package/src/reference/_generated/contracts.md +139 -101
- package/src/reference/glossary.md +74 -72
- 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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
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 === '
|
|
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
|
|
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
|
-
|
|
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']
|
|
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']
|
|
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
|
-
|
|
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
|
-
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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,
|