@elevasis/core 0.24.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +239 -86
- package/dist/index.js +474 -1346
- package/dist/knowledge/index.d.ts +57 -39
- package/dist/knowledge/index.js +1 -1
- package/dist/organization-model/index.d.ts +239 -86
- package/dist/organization-model/index.js +474 -1346
- package/dist/test-utils/index.d.ts +24 -31
- package/dist/test-utils/index.js +76 -1238
- package/package.json +1 -1
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +108 -96
- package/src/business/acquisition/api-schemas.test.ts +70 -77
- package/src/business/acquisition/api-schemas.ts +21 -42
- package/src/business/acquisition/derive-actions.test.ts +11 -21
- package/src/business/acquisition/derive-actions.ts +61 -14
- package/src/business/acquisition/ontology-validation.ts +4 -4
- package/src/business/acquisition/types.ts +7 -8
- package/src/execution/engine/llm/adapters/__tests__/openrouter.integration.test.ts +10 -10
- 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 -240
- package/src/organization-model/__tests__/content-kinds-registry.test.ts +35 -210
- package/src/organization-model/__tests__/defaults.test.ts +4 -4
- package/src/organization-model/__tests__/deprecate-helpers.test.ts +71 -0
- package/src/organization-model/__tests__/domains/actions.test.ts +12 -36
- package/src/organization-model/__tests__/domains/offerings.test.ts +13 -6
- package/src/organization-model/__tests__/domains/resources.test.ts +497 -350
- package/src/organization-model/__tests__/domains/systems.test.ts +6 -7
- package/src/organization-model/__tests__/flatten-additive-merge.test.ts +68 -80
- package/src/organization-model/__tests__/foundation.test.ts +81 -14
- package/src/organization-model/__tests__/graph.test.ts +662 -694
- package/src/organization-model/__tests__/knowledge.test.ts +31 -17
- package/src/organization-model/__tests__/lookup-helpers.test.ts +128 -438
- package/src/organization-model/__tests__/migration-helpers.test.ts +362 -591
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +68 -103
- package/src/organization-model/__tests__/published-zero-leak.test.ts +17 -0
- package/src/organization-model/__tests__/recursive-system-schema.test.ts +159 -532
- package/src/organization-model/__tests__/resolve.test.ts +88 -49
- package/src/organization-model/__tests__/scaffolders.test.ts +93 -0
- package/src/organization-model/__tests__/schema.test.ts +65 -56
- package/src/organization-model/catalogs/lead-gen.ts +0 -103
- package/src/organization-model/defaults.ts +17 -702
- package/src/organization-model/domains/actions.ts +116 -333
- package/src/organization-model/domains/knowledge.ts +15 -7
- package/src/organization-model/domains/projects.ts +4 -4
- package/src/organization-model/domains/prospecting.ts +405 -395
- package/src/organization-model/domains/resources.ts +206 -135
- package/src/organization-model/domains/sales.ts +5 -5
- package/src/organization-model/domains/systems.ts +8 -23
- package/src/organization-model/graph/build.ts +223 -294
- package/src/organization-model/graph/schema.ts +2 -3
- package/src/organization-model/graph/types.ts +12 -14
- package/src/organization-model/helpers.ts +120 -141
- package/src/organization-model/icons.ts +1 -0
- package/src/organization-model/index.ts +107 -126
- package/src/organization-model/migration-helpers.ts +211 -249
- package/src/organization-model/ontology.ts +0 -60
- package/src/organization-model/organization-graph.mdx +4 -5
- package/src/organization-model/organization-model.mdx +1 -1
- package/src/organization-model/published.ts +251 -228
- package/src/organization-model/resolve.ts +4 -5
- 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 +610 -704
- package/src/organization-model/types.ts +167 -161
- package/src/platform/constants/versions.ts +1 -1
- package/src/platform/registry/__tests__/validation.test.ts +23 -0
- package/src/platform/registry/validation.ts +13 -2
- package/src/reference/_generated/contracts.md +108 -96
- package/src/reference/glossary.md +71 -69
- package/src/organization-model/content-kinds/config.ts +0 -36
- package/src/organization-model/content-kinds/index.ts +0 -78
- package/src/organization-model/content-kinds/pipeline.ts +0 -68
- package/src/organization-model/content-kinds/registry.ts +0 -44
- package/src/organization-model/content-kinds/status.ts +0 -71
- package/src/organization-model/content-kinds/template.ts +0 -83
- package/src/organization-model/content-kinds/types.ts +0 -117
|
@@ -1,14 +1,73 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import { buildOrganizationGraph } from '../graph/build'
|
|
3
|
-
import { OrganizationGraphEdgeKindSchema, OrganizationGraphNodeKindSchema } from '../graph/schema'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import { OrganizationGraphEdgeKindSchema, OrganizationGraphNodeKindSchema } from '../graph/schema'
|
|
4
|
+
import { resolveOrganizationModel } from '../resolve'
|
|
5
|
+
import { resolveSystemConfig } from '../helpers'
|
|
6
|
+
|
|
7
|
+
const TEST_ACTION = {
|
|
8
|
+
id: 'lead-gen.company.source',
|
|
9
|
+
order: 10,
|
|
10
|
+
label: 'Source companies',
|
|
11
|
+
description: 'Import source companies from a list provider.',
|
|
12
|
+
resourceId: 'lgn-import-workflow',
|
|
13
|
+
invocations: [{ kind: 'api-endpoint' as const, method: 'POST' as const, path: '/api/prospecting/companies/source' }]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const TEST_SYSTEMS = {
|
|
17
|
+
sales: {
|
|
18
|
+
id: 'sales',
|
|
19
|
+
order: 10,
|
|
20
|
+
label: 'Sales',
|
|
21
|
+
lifecycle: 'active' as const,
|
|
22
|
+
systems: {
|
|
23
|
+
'lead-gen': {
|
|
24
|
+
id: 'sales.lead-gen',
|
|
25
|
+
order: 20,
|
|
26
|
+
label: 'Lead Gen',
|
|
27
|
+
lifecycle: 'active' as const,
|
|
28
|
+
actions: [{ actionId: TEST_ACTION.id, intent: 'exposes' as const }]
|
|
29
|
+
},
|
|
30
|
+
crm: {
|
|
31
|
+
id: 'sales.crm',
|
|
32
|
+
order: 30,
|
|
33
|
+
label: 'CRM',
|
|
34
|
+
lifecycle: 'active' as const
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
operations: {
|
|
39
|
+
id: 'operations',
|
|
40
|
+
order: 40,
|
|
41
|
+
label: 'Operations',
|
|
42
|
+
lifecycle: 'active' as const
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const TEST_ENTITIES = {
|
|
47
|
+
'crm.deal': {
|
|
48
|
+
id: 'crm.deal',
|
|
49
|
+
order: 10,
|
|
50
|
+
label: 'Deal',
|
|
51
|
+
ownedBySystemId: 'sales.crm',
|
|
52
|
+
links: [{ toEntity: 'crm.contact', kind: 'has-many' as const, label: 'contacts' }]
|
|
53
|
+
},
|
|
54
|
+
'crm.contact': {
|
|
55
|
+
id: 'crm.contact',
|
|
56
|
+
order: 20,
|
|
57
|
+
label: 'CRM Contact',
|
|
58
|
+
ownedBySystemId: 'sales.crm'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const BASE_GRAPH_MODEL = {
|
|
63
|
+
systems: TEST_SYSTEMS,
|
|
64
|
+
actions: { [TEST_ACTION.id]: TEST_ACTION },
|
|
65
|
+
entities: TEST_ENTITIES
|
|
66
|
+
}
|
|
8
67
|
|
|
9
68
|
describe('organization graph', () => {
|
|
10
69
|
it('emits system nodes from the systems domain', () => {
|
|
11
|
-
const graph = buildOrganizationGraph({ organizationModel: resolveOrganizationModel() })
|
|
70
|
+
const graph = buildOrganizationGraph({ organizationModel: resolveOrganizationModel(BASE_GRAPH_MODEL) })
|
|
12
71
|
|
|
13
72
|
expect(graph.nodes.find((node) => node.id === 'system:sales.crm')).toMatchObject({
|
|
14
73
|
kind: 'system',
|
|
@@ -115,468 +174,431 @@ describe('organization graph', () => {
|
|
|
115
174
|
expect(() => OrganizationGraphEdgeKindSchema.parse('links')).not.toThrow()
|
|
116
175
|
expect(() => OrganizationGraphEdgeKindSchema.parse('affects')).not.toThrow()
|
|
117
176
|
expect(() => OrganizationGraphEdgeKindSchema.parse('emits')).not.toThrow()
|
|
118
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse('originates_from')).not.toThrow()
|
|
119
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse('triggers')).not.toThrow()
|
|
120
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse('approval')).not.toThrow()
|
|
177
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('originates_from')).not.toThrow()
|
|
178
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('triggers')).not.toThrow()
|
|
179
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('approval')).not.toThrow()
|
|
121
180
|
expect(() => OrganizationGraphNodeKindSchema.parse('surface')).not.toThrow()
|
|
122
181
|
expect(() => OrganizationGraphNodeKindSchema.parse('customer-segment')).not.toThrow()
|
|
123
182
|
expect(() => OrganizationGraphNodeKindSchema.parse('offering')).not.toThrow()
|
|
124
|
-
expect(() => OrganizationGraphNodeKindSchema.parse('goal')).not.toThrow()
|
|
125
|
-
expect(() => OrganizationGraphNodeKindSchema.parse('navigation-group')).not.toThrow()
|
|
126
|
-
expect(() => OrganizationGraphNodeKindSchema.parse('ontology')).not.toThrow()
|
|
127
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse('actions')).not.toThrow()
|
|
128
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse('reads')).not.toThrow()
|
|
129
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse('writes')).not.toThrow()
|
|
130
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse('uses_catalog')).not.toThrow()
|
|
131
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse('exposes')).toThrow()
|
|
132
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse(['operates', 'on'].join('-'))).toThrow()
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
it('projects compiled ontology records as ontology-native nodes and edges', () => {
|
|
136
|
-
const model = resolveOrganizationModel({
|
|
137
|
-
systems: {
|
|
138
|
-
'test.ontology': {
|
|
139
|
-
id: 'test.ontology',
|
|
140
|
-
order: 10,
|
|
141
|
-
label: 'Ontology System',
|
|
142
|
-
enabled: true,
|
|
143
|
-
ontology: {
|
|
144
|
-
objectTypes: {
|
|
145
|
-
'test.ontology:object/deal': {
|
|
146
|
-
id: 'test.ontology:object/deal',
|
|
147
|
-
label: 'Deal'
|
|
148
|
-
},
|
|
149
|
-
'test.ontology:object/contact': {
|
|
150
|
-
id: 'test.ontology:object/contact',
|
|
151
|
-
label: 'Contact'
|
|
152
|
-
}
|
|
153
|
-
},
|
|
154
|
-
linkTypes: {
|
|
155
|
-
'test.ontology:link/deal-contact': {
|
|
156
|
-
id: 'test.ontology:link/deal-contact',
|
|
157
|
-
label: 'Deal Contact',
|
|
158
|
-
from: 'test.ontology:object/deal',
|
|
159
|
-
to: 'test.ontology:object/contact'
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
actionTypes: {
|
|
163
|
-
'test.ontology:action/update-deal': {
|
|
164
|
-
id: 'test.ontology:action/update-deal',
|
|
165
|
-
label: 'Update Deal',
|
|
166
|
-
actsOn: ['test.ontology:object/deal']
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
catalogTypes: {
|
|
170
|
-
'test.ontology:catalog/pipeline': {
|
|
171
|
-
id: 'test.ontology:catalog/pipeline',
|
|
172
|
-
label: 'Pipeline',
|
|
173
|
-
appliesTo: 'test.ontology:object/deal'
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
})
|
|
180
|
-
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
181
|
-
|
|
182
|
-
expect(graph.nodes.find((node) => node.id === 'ontology:test.ontology:object/deal')).toMatchObject({
|
|
183
|
-
kind: 'ontology',
|
|
184
|
-
sourceId: 'test.ontology:object/deal',
|
|
185
|
-
label: 'Deal',
|
|
186
|
-
ontologyKind: 'object'
|
|
187
|
-
})
|
|
188
|
-
expect(
|
|
189
|
-
graph.edges.find(
|
|
190
|
-
(edge) =>
|
|
191
|
-
edge.kind === 'contains' &&
|
|
192
|
-
edge.sourceId === 'system:test.ontology' &&
|
|
193
|
-
edge.targetId === 'ontology:test.ontology:object/deal'
|
|
194
|
-
)
|
|
195
|
-
).toBeDefined()
|
|
196
|
-
expect(
|
|
197
|
-
graph.edges.find(
|
|
198
|
-
(edge) =>
|
|
199
|
-
edge.kind === 'links' &&
|
|
200
|
-
edge.sourceId === 'ontology:test.ontology:object/deal' &&
|
|
201
|
-
edge.targetId === 'ontology:test.ontology:object/contact'
|
|
202
|
-
)
|
|
203
|
-
).toMatchObject({ label: 'Deal Contact' })
|
|
204
|
-
expect(
|
|
205
|
-
graph.edges.find(
|
|
206
|
-
(edge) =>
|
|
207
|
-
edge.kind === 'affects' &&
|
|
208
|
-
edge.sourceId === 'ontology:test.ontology:action/update-deal' &&
|
|
209
|
-
edge.targetId === 'ontology:test.ontology:object/deal'
|
|
210
|
-
)
|
|
211
|
-
).toBeDefined()
|
|
212
|
-
expect(
|
|
213
|
-
graph.edges.find(
|
|
214
|
-
(edge) =>
|
|
215
|
-
edge.kind === 'applies_to' &&
|
|
216
|
-
edge.sourceId === 'ontology:test.ontology:catalog/pipeline' &&
|
|
217
|
-
edge.targetId === 'ontology:test.ontology:object/deal'
|
|
218
|
-
)
|
|
219
|
-
).toBeDefined()
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
it('projects ontology-native records from recursively authored systems without content-node bridge input', () => {
|
|
223
|
-
const model = resolveOrganizationModel({
|
|
224
|
-
systems: {
|
|
225
|
-
operations: {
|
|
226
|
-
id: 'operations',
|
|
227
|
-
order: 10,
|
|
228
|
-
label: 'Operations',
|
|
229
|
-
enabled: true,
|
|
230
|
-
systems: {
|
|
231
|
-
delivery: {
|
|
232
|
-
id: 'operations.delivery',
|
|
233
|
-
order: 20,
|
|
234
|
-
label: 'Delivery',
|
|
235
|
-
enabled: true,
|
|
236
|
-
ontology: {
|
|
237
|
-
objectTypes: {
|
|
238
|
-
'operations.delivery:object/project': {
|
|
239
|
-
id: 'operations.delivery:object/project',
|
|
240
|
-
label: 'Project'
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
catalogTypes: {
|
|
244
|
-
'operations.delivery:catalog/project-status': {
|
|
245
|
-
id: 'operations.delivery:catalog/project-status',
|
|
246
|
-
label: 'Project Status',
|
|
247
|
-
kind: 'status-flow',
|
|
248
|
-
appliesTo: 'operations.delivery:object/project'
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
})
|
|
257
|
-
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
258
|
-
|
|
259
|
-
expect(graph.nodes.find((node) => node.id === 'system:operations.delivery')).toMatchObject({
|
|
260
|
-
kind: 'system',
|
|
261
|
-
sourceId: 'operations.delivery',
|
|
262
|
-
label: 'Delivery'
|
|
263
|
-
})
|
|
264
|
-
expect(
|
|
265
|
-
graph.edges.find(
|
|
266
|
-
(edge) =>
|
|
267
|
-
edge.kind === 'contains' &&
|
|
268
|
-
edge.sourceId === 'system:operations' &&
|
|
269
|
-
edge.targetId === 'system:operations.delivery'
|
|
270
|
-
)
|
|
271
|
-
).toBeDefined()
|
|
272
|
-
expect(graph.nodes.find((node) => node.id === 'ontology:operations.delivery:catalog/project-status')).toMatchObject(
|
|
273
|
-
{
|
|
274
|
-
kind: 'ontology',
|
|
275
|
-
sourceId: 'operations.delivery:catalog/project-status',
|
|
276
|
-
ontologyKind: 'catalog'
|
|
277
|
-
}
|
|
278
|
-
)
|
|
279
|
-
expect(
|
|
280
|
-
graph.edges.find(
|
|
281
|
-
(edge) =>
|
|
282
|
-
edge.kind === 'applies_to' &&
|
|
283
|
-
edge.sourceId === 'ontology:operations.delivery:catalog/project-status' &&
|
|
284
|
-
edge.targetId === 'ontology:operations.delivery:object/project'
|
|
285
|
-
)
|
|
286
|
-
).toBeDefined()
|
|
287
|
-
expect(graph.nodes.some((node) => node.id.startsWith('content-node:operations.delivery:'))).toBe(false)
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
it('keeps subsystems as a compatibility alias for recursive graph traversal', () => {
|
|
291
|
-
const model = resolveOrganizationModel({
|
|
292
|
-
systems: {
|
|
293
|
-
legacy: {
|
|
294
|
-
id: 'legacy',
|
|
295
|
-
order: 10,
|
|
296
|
-
label: 'Legacy',
|
|
297
|
-
enabled: true,
|
|
298
|
-
subsystems: {
|
|
299
|
-
child: {
|
|
300
|
-
id: 'legacy.child',
|
|
301
|
-
order: 20,
|
|
302
|
-
label: 'Legacy Child',
|
|
303
|
-
enabled: true
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
})
|
|
309
|
-
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
310
|
-
|
|
311
|
-
expect(graph.nodes.find((node) => node.id === 'system:legacy.child')).toMatchObject({
|
|
312
|
-
kind: 'system',
|
|
313
|
-
sourceId: 'legacy.child',
|
|
314
|
-
label: 'Legacy Child'
|
|
315
|
-
})
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
it('resolves first-class system config without requiring config:kv content', () => {
|
|
319
|
-
const model = resolveOrganizationModel({
|
|
320
|
-
systems: {
|
|
321
|
-
configurable: {
|
|
322
|
-
id: 'configurable',
|
|
323
|
-
order: 10,
|
|
324
|
-
label: 'Configurable',
|
|
325
|
-
enabled: true,
|
|
326
|
-
config: {
|
|
327
|
-
retries: 3,
|
|
328
|
-
mode: 'direct',
|
|
329
|
-
nested: {
|
|
330
|
-
source: 'system.config',
|
|
331
|
-
enabled: true
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
expect(resolveSystemConfig(model, 'configurable')).toEqual({
|
|
339
|
-
retries: 3,
|
|
340
|
-
mode: 'direct',
|
|
341
|
-
nested: {
|
|
342
|
-
source: 'system.config',
|
|
343
|
-
enabled: true
|
|
344
|
-
}
|
|
345
|
-
})
|
|
346
|
-
expect(model.systems.configurable?.content).toBeUndefined()
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
it('lets first-class system config override retained config:kv compatibility input', () => {
|
|
350
|
-
const model = resolveOrganizationModel({
|
|
351
|
-
systems: {
|
|
352
|
-
configurable: {
|
|
353
|
-
id: 'configurable',
|
|
354
|
-
order: 10,
|
|
355
|
-
label: 'Configurable',
|
|
356
|
-
enabled: true,
|
|
357
|
-
config: {
|
|
358
|
-
retries: 3,
|
|
359
|
-
nested: {
|
|
360
|
-
direct: true
|
|
361
|
-
}
|
|
362
|
-
},
|
|
363
|
-
content: {
|
|
364
|
-
settings: {
|
|
365
|
-
kind: 'config',
|
|
366
|
-
type: 'kv',
|
|
367
|
-
label: 'Settings',
|
|
368
|
-
data: {
|
|
369
|
-
entries: {
|
|
370
|
-
enabled: true,
|
|
371
|
-
retries: 1,
|
|
372
|
-
mode: 'bridge'
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
expect(resolveSystemConfig(model, 'configurable')).toEqual({
|
|
382
|
-
enabled: true,
|
|
383
|
-
retries: 3,
|
|
384
|
-
mode: 'bridge',
|
|
385
|
-
nested: {
|
|
386
|
-
direct: true
|
|
387
|
-
}
|
|
388
|
-
})
|
|
389
|
-
})
|
|
390
|
-
|
|
391
|
-
it('projects resource ontology bindings as resource-to-ontology edges', () => {
|
|
392
|
-
const model = resolveOrganizationModel({
|
|
393
|
-
systems: {
|
|
394
|
-
'test.bindings': {
|
|
395
|
-
id: 'test.bindings',
|
|
396
|
-
order: 10,
|
|
397
|
-
label: 'Binding System',
|
|
398
|
-
enabled: true,
|
|
399
|
-
ontology: {
|
|
400
|
-
objectTypes: {
|
|
401
|
-
'test.bindings:object/deal': {
|
|
402
|
-
id: 'test.bindings:object/deal',
|
|
403
|
-
label: 'Deal'
|
|
404
|
-
}
|
|
405
|
-
},
|
|
406
|
-
actionTypes: {
|
|
407
|
-
'test.bindings:action/update-deal': {
|
|
408
|
-
id: 'test.bindings:action/update-deal',
|
|
409
|
-
label: 'Update Deal'
|
|
410
|
-
}
|
|
411
|
-
},
|
|
412
|
-
catalogTypes: {
|
|
413
|
-
'test.bindings:catalog/pipeline': {
|
|
414
|
-
id: 'test.bindings:catalog/pipeline',
|
|
415
|
-
label: 'Pipeline'
|
|
416
|
-
}
|
|
417
|
-
},
|
|
418
|
-
eventTypes: {
|
|
419
|
-
'test.bindings:event/deal-updated': {
|
|
420
|
-
id: 'test.bindings:event/deal-updated',
|
|
421
|
-
label: 'Deal Updated'
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
},
|
|
427
|
-
resources: {
|
|
428
|
-
'deal-workflow': {
|
|
429
|
-
id: 'deal-workflow',
|
|
430
|
-
order: 10,
|
|
431
|
-
kind: 'workflow',
|
|
432
|
-
systemPath: 'test.bindings',
|
|
433
|
-
status: 'active',
|
|
434
|
-
ontology: {
|
|
435
|
-
actions: ['test.bindings:action/update-deal'],
|
|
436
|
-
primaryAction: 'test.bindings:action/update-deal',
|
|
437
|
-
reads: ['test.bindings:object/deal'],
|
|
438
|
-
writes: ['test.bindings:object/deal'],
|
|
439
|
-
usesCatalogs: ['test.bindings:catalog/pipeline'],
|
|
440
|
-
emits: ['test.bindings:event/deal-updated']
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
})
|
|
445
|
-
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
446
|
-
|
|
447
|
-
expect(
|
|
448
|
-
graph.edges.find(
|
|
449
|
-
(edge) =>
|
|
450
|
-
edge.kind === 'actions' &&
|
|
451
|
-
edge.sourceId === 'resource:deal-workflow' &&
|
|
452
|
-
edge.targetId === 'ontology:test.bindings:action/update-deal'
|
|
453
|
-
)
|
|
454
|
-
).toBeDefined()
|
|
455
|
-
expect(
|
|
456
|
-
graph.edges.find(
|
|
457
|
-
(edge) =>
|
|
458
|
-
edge.kind === 'reads' &&
|
|
459
|
-
edge.sourceId === 'resource:deal-workflow' &&
|
|
460
|
-
edge.targetId === 'ontology:test.bindings:object/deal'
|
|
461
|
-
)
|
|
462
|
-
).toBeDefined()
|
|
463
|
-
expect(
|
|
464
|
-
graph.edges.find(
|
|
465
|
-
(edge) =>
|
|
466
|
-
edge.kind === 'writes' &&
|
|
467
|
-
edge.sourceId === 'resource:deal-workflow' &&
|
|
468
|
-
edge.targetId === 'ontology:test.bindings:object/deal'
|
|
469
|
-
)
|
|
470
|
-
).toBeDefined()
|
|
471
|
-
expect(
|
|
472
|
-
graph.edges.find(
|
|
473
|
-
(edge) =>
|
|
474
|
-
edge.kind === 'uses_catalog' &&
|
|
475
|
-
edge.sourceId === 'resource:deal-workflow' &&
|
|
476
|
-
edge.targetId === 'ontology:test.bindings:catalog/pipeline'
|
|
477
|
-
)
|
|
478
|
-
).toBeDefined()
|
|
479
|
-
expect(
|
|
480
|
-
graph.edges.find(
|
|
481
|
-
(edge) =>
|
|
482
|
-
edge.kind === 'emits' &&
|
|
483
|
-
edge.sourceId === 'resource:deal-workflow' &&
|
|
484
|
-
edge.targetId === 'ontology:test.bindings:event/deal-updated'
|
|
485
|
-
)
|
|
486
|
-
).toBeDefined()
|
|
487
|
-
})
|
|
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', () => {
|
|
183
|
+
expect(() => OrganizationGraphNodeKindSchema.parse('goal')).not.toThrow()
|
|
184
|
+
expect(() => OrganizationGraphNodeKindSchema.parse('navigation-group')).not.toThrow()
|
|
185
|
+
expect(() => OrganizationGraphNodeKindSchema.parse('ontology')).not.toThrow()
|
|
186
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('actions')).not.toThrow()
|
|
187
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('reads')).not.toThrow()
|
|
188
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('writes')).not.toThrow()
|
|
189
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('uses_catalog')).not.toThrow()
|
|
190
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('exposes')).toThrow()
|
|
191
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse(['operates', 'on'].join('-'))).toThrow()
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('projects compiled ontology records as ontology-native nodes and edges', () => {
|
|
195
|
+
const model = resolveOrganizationModel({
|
|
196
|
+
systems: {
|
|
197
|
+
'test.ontology': {
|
|
198
|
+
id: 'test.ontology',
|
|
199
|
+
order: 10,
|
|
200
|
+
label: 'Ontology System',
|
|
201
|
+
enabled: true,
|
|
202
|
+
ontology: {
|
|
203
|
+
objectTypes: {
|
|
204
|
+
'test.ontology:object/deal': {
|
|
205
|
+
id: 'test.ontology:object/deal',
|
|
206
|
+
label: 'Deal'
|
|
207
|
+
},
|
|
208
|
+
'test.ontology:object/contact': {
|
|
209
|
+
id: 'test.ontology:object/contact',
|
|
210
|
+
label: 'Contact'
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
linkTypes: {
|
|
214
|
+
'test.ontology:link/deal-contact': {
|
|
215
|
+
id: 'test.ontology:link/deal-contact',
|
|
216
|
+
label: 'Deal Contact',
|
|
217
|
+
from: 'test.ontology:object/deal',
|
|
218
|
+
to: 'test.ontology:object/contact'
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
actionTypes: {
|
|
222
|
+
'test.ontology:action/update-deal': {
|
|
223
|
+
id: 'test.ontology:action/update-deal',
|
|
224
|
+
label: 'Update Deal',
|
|
225
|
+
actsOn: ['test.ontology:object/deal']
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
catalogTypes: {
|
|
229
|
+
'test.ontology:catalog/pipeline': {
|
|
230
|
+
id: 'test.ontology:catalog/pipeline',
|
|
231
|
+
label: 'Pipeline',
|
|
232
|
+
appliesTo: 'test.ontology:object/deal'
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
240
|
+
|
|
241
|
+
expect(graph.nodes.find((node) => node.id === 'ontology:test.ontology:object/deal')).toMatchObject({
|
|
242
|
+
kind: 'ontology',
|
|
243
|
+
sourceId: 'test.ontology:object/deal',
|
|
244
|
+
label: 'Deal',
|
|
245
|
+
ontologyKind: 'object'
|
|
246
|
+
})
|
|
247
|
+
expect(
|
|
248
|
+
graph.edges.find(
|
|
249
|
+
(edge) =>
|
|
250
|
+
edge.kind === 'contains' &&
|
|
251
|
+
edge.sourceId === 'system:test.ontology' &&
|
|
252
|
+
edge.targetId === 'ontology:test.ontology:object/deal'
|
|
253
|
+
)
|
|
254
|
+
).toBeDefined()
|
|
255
|
+
expect(
|
|
256
|
+
graph.edges.find(
|
|
257
|
+
(edge) =>
|
|
258
|
+
edge.kind === 'links' &&
|
|
259
|
+
edge.sourceId === 'ontology:test.ontology:object/deal' &&
|
|
260
|
+
edge.targetId === 'ontology:test.ontology:object/contact'
|
|
261
|
+
)
|
|
262
|
+
).toMatchObject({ label: 'Deal Contact' })
|
|
263
|
+
expect(
|
|
264
|
+
graph.edges.find(
|
|
265
|
+
(edge) =>
|
|
266
|
+
edge.kind === 'affects' &&
|
|
267
|
+
edge.sourceId === 'ontology:test.ontology:action/update-deal' &&
|
|
268
|
+
edge.targetId === 'ontology:test.ontology:object/deal'
|
|
269
|
+
)
|
|
270
|
+
).toBeDefined()
|
|
271
|
+
expect(
|
|
272
|
+
graph.edges.find(
|
|
273
|
+
(edge) =>
|
|
274
|
+
edge.kind === 'applies_to' &&
|
|
275
|
+
edge.sourceId === 'ontology:test.ontology:catalog/pipeline' &&
|
|
276
|
+
edge.targetId === 'ontology:test.ontology:object/deal'
|
|
277
|
+
)
|
|
278
|
+
).toBeDefined()
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
it('projects ontology-native records from recursively authored systems without content-node bridge input', () => {
|
|
282
|
+
const model = resolveOrganizationModel({
|
|
283
|
+
systems: {
|
|
284
|
+
operations: {
|
|
285
|
+
id: 'operations',
|
|
286
|
+
order: 10,
|
|
287
|
+
label: 'Operations',
|
|
288
|
+
enabled: true,
|
|
289
|
+
systems: {
|
|
290
|
+
delivery: {
|
|
291
|
+
id: 'operations.delivery',
|
|
292
|
+
order: 20,
|
|
293
|
+
label: 'Delivery',
|
|
294
|
+
enabled: true,
|
|
295
|
+
ontology: {
|
|
296
|
+
objectTypes: {
|
|
297
|
+
'operations.delivery:object/project': {
|
|
298
|
+
id: 'operations.delivery:object/project',
|
|
299
|
+
label: 'Project'
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
catalogTypes: {
|
|
303
|
+
'operations.delivery:catalog/project-status': {
|
|
304
|
+
id: 'operations.delivery:catalog/project-status',
|
|
305
|
+
label: 'Project Status',
|
|
306
|
+
kind: 'status-flow',
|
|
307
|
+
appliesTo: 'operations.delivery:object/project'
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
317
|
+
|
|
318
|
+
expect(graph.nodes.find((node) => node.id === 'system:operations.delivery')).toMatchObject({
|
|
319
|
+
kind: 'system',
|
|
320
|
+
sourceId: 'operations.delivery',
|
|
321
|
+
label: 'Delivery'
|
|
322
|
+
})
|
|
323
|
+
expect(
|
|
324
|
+
graph.edges.find(
|
|
325
|
+
(edge) =>
|
|
326
|
+
edge.kind === 'contains' &&
|
|
327
|
+
edge.sourceId === 'system:operations' &&
|
|
328
|
+
edge.targetId === 'system:operations.delivery'
|
|
329
|
+
)
|
|
330
|
+
).toBeDefined()
|
|
331
|
+
expect(graph.nodes.find((node) => node.id === 'ontology:operations.delivery:catalog/project-status')).toMatchObject(
|
|
332
|
+
{
|
|
333
|
+
kind: 'ontology',
|
|
334
|
+
sourceId: 'operations.delivery:catalog/project-status',
|
|
335
|
+
ontologyKind: 'catalog'
|
|
336
|
+
}
|
|
337
|
+
)
|
|
338
|
+
expect(
|
|
339
|
+
graph.edges.find(
|
|
340
|
+
(edge) =>
|
|
341
|
+
edge.kind === 'applies_to' &&
|
|
342
|
+
edge.sourceId === 'ontology:operations.delivery:catalog/project-status' &&
|
|
343
|
+
edge.targetId === 'ontology:operations.delivery:object/project'
|
|
344
|
+
)
|
|
345
|
+
).toBeDefined()
|
|
346
|
+
expect(graph.nodes.some((node) => node.id.startsWith('content-node:operations.delivery:'))).toBe(false)
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('keeps subsystems as a compatibility alias for recursive graph traversal', () => {
|
|
579
350
|
const model = resolveOrganizationModel({
|
|
351
|
+
systems: {
|
|
352
|
+
legacy: {
|
|
353
|
+
id: 'legacy',
|
|
354
|
+
order: 10,
|
|
355
|
+
label: 'Legacy',
|
|
356
|
+
enabled: true,
|
|
357
|
+
subsystems: {
|
|
358
|
+
child: {
|
|
359
|
+
id: 'legacy.child',
|
|
360
|
+
order: 20,
|
|
361
|
+
label: 'Legacy Child',
|
|
362
|
+
enabled: true
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
369
|
+
|
|
370
|
+
expect(graph.nodes.find((node) => node.id === 'system:legacy.child')).toMatchObject({
|
|
371
|
+
kind: 'system',
|
|
372
|
+
sourceId: 'legacy.child',
|
|
373
|
+
label: 'Legacy Child'
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('resolves first-class system config without requiring config:kv content', () => {
|
|
378
|
+
const model = resolveOrganizationModel({
|
|
379
|
+
systems: {
|
|
380
|
+
configurable: {
|
|
381
|
+
id: 'configurable',
|
|
382
|
+
order: 10,
|
|
383
|
+
label: 'Configurable',
|
|
384
|
+
enabled: true,
|
|
385
|
+
config: {
|
|
386
|
+
retries: 3,
|
|
387
|
+
mode: 'direct',
|
|
388
|
+
nested: {
|
|
389
|
+
source: 'system.config',
|
|
390
|
+
enabled: true
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
expect(resolveSystemConfig(model, 'configurable')).toEqual({
|
|
398
|
+
retries: 3,
|
|
399
|
+
mode: 'direct',
|
|
400
|
+
nested: {
|
|
401
|
+
source: 'system.config',
|
|
402
|
+
enabled: true
|
|
403
|
+
}
|
|
404
|
+
})
|
|
405
|
+
expect(model.systems.configurable?.content).toBeUndefined()
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
it('projects resource ontology bindings as resource-to-ontology edges', () => {
|
|
410
|
+
const model = resolveOrganizationModel({
|
|
411
|
+
systems: {
|
|
412
|
+
'test.bindings': {
|
|
413
|
+
id: 'test.bindings',
|
|
414
|
+
order: 10,
|
|
415
|
+
label: 'Binding System',
|
|
416
|
+
enabled: true,
|
|
417
|
+
ontology: {
|
|
418
|
+
objectTypes: {
|
|
419
|
+
'test.bindings:object/deal': {
|
|
420
|
+
id: 'test.bindings:object/deal',
|
|
421
|
+
label: 'Deal'
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
actionTypes: {
|
|
425
|
+
'test.bindings:action/update-deal': {
|
|
426
|
+
id: 'test.bindings:action/update-deal',
|
|
427
|
+
label: 'Update Deal'
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
catalogTypes: {
|
|
431
|
+
'test.bindings:catalog/pipeline': {
|
|
432
|
+
id: 'test.bindings:catalog/pipeline',
|
|
433
|
+
label: 'Pipeline'
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
eventTypes: {
|
|
437
|
+
'test.bindings:event/deal-updated': {
|
|
438
|
+
id: 'test.bindings:event/deal-updated',
|
|
439
|
+
label: 'Deal Updated'
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
resources: {
|
|
446
|
+
'deal-workflow': {
|
|
447
|
+
id: 'deal-workflow',
|
|
448
|
+
order: 10,
|
|
449
|
+
kind: 'workflow',
|
|
450
|
+
systemPath: 'test.bindings',
|
|
451
|
+
status: 'active',
|
|
452
|
+
ontology: {
|
|
453
|
+
actions: ['test.bindings:action/update-deal'],
|
|
454
|
+
primaryAction: 'test.bindings:action/update-deal',
|
|
455
|
+
reads: ['test.bindings:object/deal'],
|
|
456
|
+
writes: ['test.bindings:object/deal'],
|
|
457
|
+
usesCatalogs: ['test.bindings:catalog/pipeline'],
|
|
458
|
+
emits: ['test.bindings:event/deal-updated']
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
})
|
|
463
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
464
|
+
|
|
465
|
+
expect(
|
|
466
|
+
graph.edges.find(
|
|
467
|
+
(edge) =>
|
|
468
|
+
edge.kind === 'actions' &&
|
|
469
|
+
edge.sourceId === 'resource:deal-workflow' &&
|
|
470
|
+
edge.targetId === 'ontology:test.bindings:action/update-deal'
|
|
471
|
+
)
|
|
472
|
+
).toBeDefined()
|
|
473
|
+
expect(
|
|
474
|
+
graph.edges.find(
|
|
475
|
+
(edge) =>
|
|
476
|
+
edge.kind === 'reads' &&
|
|
477
|
+
edge.sourceId === 'resource:deal-workflow' &&
|
|
478
|
+
edge.targetId === 'ontology:test.bindings:object/deal'
|
|
479
|
+
)
|
|
480
|
+
).toBeDefined()
|
|
481
|
+
expect(
|
|
482
|
+
graph.edges.find(
|
|
483
|
+
(edge) =>
|
|
484
|
+
edge.kind === 'writes' &&
|
|
485
|
+
edge.sourceId === 'resource:deal-workflow' &&
|
|
486
|
+
edge.targetId === 'ontology:test.bindings:object/deal'
|
|
487
|
+
)
|
|
488
|
+
).toBeDefined()
|
|
489
|
+
expect(
|
|
490
|
+
graph.edges.find(
|
|
491
|
+
(edge) =>
|
|
492
|
+
edge.kind === 'uses_catalog' &&
|
|
493
|
+
edge.sourceId === 'resource:deal-workflow' &&
|
|
494
|
+
edge.targetId === 'ontology:test.bindings:catalog/pipeline'
|
|
495
|
+
)
|
|
496
|
+
).toBeDefined()
|
|
497
|
+
expect(
|
|
498
|
+
graph.edges.find(
|
|
499
|
+
(edge) =>
|
|
500
|
+
edge.kind === 'emits' &&
|
|
501
|
+
edge.sourceId === 'resource:deal-workflow' &&
|
|
502
|
+
edge.targetId === 'ontology:test.bindings:event/deal-updated'
|
|
503
|
+
)
|
|
504
|
+
).toBeDefined()
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
it('projects topology relationships without collapsing ontology binding edges', () => {
|
|
508
|
+
const model = resolveOrganizationModel({
|
|
509
|
+
systems: {
|
|
510
|
+
'test.topology': {
|
|
511
|
+
id: 'test.topology',
|
|
512
|
+
order: 10,
|
|
513
|
+
label: 'Topology System',
|
|
514
|
+
enabled: true,
|
|
515
|
+
ontology: {
|
|
516
|
+
actionTypes: {
|
|
517
|
+
'test.topology:action/discover-email': {
|
|
518
|
+
id: 'test.topology:action/discover-email',
|
|
519
|
+
label: 'Discover Email'
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
resources: {
|
|
526
|
+
'email-discovery': {
|
|
527
|
+
id: 'email-discovery',
|
|
528
|
+
order: 10,
|
|
529
|
+
kind: 'workflow',
|
|
530
|
+
systemPath: 'test.topology',
|
|
531
|
+
status: 'active',
|
|
532
|
+
ontology: {
|
|
533
|
+
actions: ['test.topology:action/discover-email'],
|
|
534
|
+
primaryAction: 'test.topology:action/discover-email'
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
'email-verification': {
|
|
538
|
+
id: 'email-verification',
|
|
539
|
+
order: 20,
|
|
540
|
+
kind: 'workflow',
|
|
541
|
+
systemPath: 'test.topology',
|
|
542
|
+
status: 'active'
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
topology: {
|
|
546
|
+
version: 1,
|
|
547
|
+
relationships: {
|
|
548
|
+
'email-discovery-triggers-email-verification': {
|
|
549
|
+
from: { kind: 'resource', id: 'email-discovery' },
|
|
550
|
+
kind: 'triggers',
|
|
551
|
+
to: { kind: 'resource', id: 'email-verification' },
|
|
552
|
+
required: true
|
|
553
|
+
},
|
|
554
|
+
'email-verification-approval-human-review': {
|
|
555
|
+
from: { kind: 'resource', id: 'email-verification' },
|
|
556
|
+
kind: 'approval',
|
|
557
|
+
to: { kind: 'humanCheckpoint', id: 'email-review' }
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
})
|
|
562
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
563
|
+
|
|
564
|
+
expect(
|
|
565
|
+
graph.edges.find(
|
|
566
|
+
(edge) =>
|
|
567
|
+
edge.kind === 'actions' &&
|
|
568
|
+
edge.sourceId === 'resource:email-discovery' &&
|
|
569
|
+
edge.targetId === 'ontology:test.topology:action/discover-email'
|
|
570
|
+
)
|
|
571
|
+
).toBeDefined()
|
|
572
|
+
expect(
|
|
573
|
+
graph.edges.find(
|
|
574
|
+
(edge) =>
|
|
575
|
+
edge.kind === 'triggers' &&
|
|
576
|
+
edge.relationshipType === 'triggers' &&
|
|
577
|
+
edge.sourceId === 'resource:email-discovery' &&
|
|
578
|
+
edge.targetId === 'resource:email-verification'
|
|
579
|
+
)
|
|
580
|
+
).toBeDefined()
|
|
581
|
+
expect(graph.nodes.find((node) => node.id === 'resource:email-review')).toMatchObject({
|
|
582
|
+
kind: 'resource',
|
|
583
|
+
resourceType: 'human_checkpoint'
|
|
584
|
+
})
|
|
585
|
+
expect(
|
|
586
|
+
graph.edges.find(
|
|
587
|
+
(edge) =>
|
|
588
|
+
edge.kind === 'approval' &&
|
|
589
|
+
edge.relationshipType === 'approval' &&
|
|
590
|
+
edge.sourceId === 'resource:email-verification' &&
|
|
591
|
+
edge.targetId === 'resource:email-review'
|
|
592
|
+
)
|
|
593
|
+
).toBeDefined()
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
it('projects surface and navigation-group nodes from recursive sidebar leaves', () => {
|
|
597
|
+
const model = resolveOrganizationModel({
|
|
598
|
+
systems: {
|
|
599
|
+
sales: TEST_SYSTEMS.sales
|
|
600
|
+
},
|
|
601
|
+
actions: { [TEST_ACTION.id]: TEST_ACTION },
|
|
580
602
|
navigation: {
|
|
581
603
|
sidebar: {
|
|
582
604
|
primary: {
|
|
@@ -699,8 +721,48 @@ describe('organization graph', () => {
|
|
|
699
721
|
})
|
|
700
722
|
})
|
|
701
723
|
|
|
702
|
-
|
|
703
|
-
it('projects
|
|
724
|
+
|
|
725
|
+
it('projects action nodes from the actions domain with maps_to edges to resources', () => {
|
|
726
|
+
const action = TEST_ACTION
|
|
727
|
+
const graph = buildOrganizationGraph({
|
|
728
|
+
organizationModel: resolveOrganizationModel({
|
|
729
|
+
systems: TEST_SYSTEMS,
|
|
730
|
+
actions: { [action.id]: action },
|
|
731
|
+
resources: {
|
|
732
|
+
[action.resourceId]: {
|
|
733
|
+
id: action.resourceId,
|
|
734
|
+
order: 10,
|
|
735
|
+
kind: 'workflow',
|
|
736
|
+
systemPath: 'sales.lead-gen',
|
|
737
|
+
status: 'active'
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
})
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
const actionNodes = graph.nodes.filter((node) => node.kind === 'action')
|
|
744
|
+
expect(actionNodes).toHaveLength(1)
|
|
745
|
+
|
|
746
|
+
const node = graph.nodes.find((n) => n.id === `action:${action.id}`)
|
|
747
|
+
expect(node).toMatchObject({
|
|
748
|
+
kind: 'action',
|
|
749
|
+
sourceId: action.id,
|
|
750
|
+
label: action.label,
|
|
751
|
+
description: action.description
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
expect(
|
|
755
|
+
graph.edges.find(
|
|
756
|
+
(edge) =>
|
|
757
|
+
edge.kind === 'maps_to' &&
|
|
758
|
+
edge.sourceId === `action:${action.id}` &&
|
|
759
|
+
edge.targetId === `resource:${action.resourceId}`
|
|
760
|
+
)
|
|
761
|
+
).toBeDefined()
|
|
762
|
+
|
|
763
|
+
expect(graph.nodes.find((n) => n.id === `resource:${action.resourceId}`)).toBeDefined()
|
|
764
|
+
})
|
|
765
|
+
it('projects ontology catalog nodes without content-node bridge output', () => {
|
|
704
766
|
const model = resolveOrganizationModel({
|
|
705
767
|
systems: {
|
|
706
768
|
'test.pipeline-sys': {
|
|
@@ -708,19 +770,24 @@ describe('organization graph', () => {
|
|
|
708
770
|
order: 10,
|
|
709
771
|
label: 'Pipeline System',
|
|
710
772
|
enabled: true,
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
773
|
+
ontology: {
|
|
774
|
+
objectTypes: {
|
|
775
|
+
'test.pipeline-sys:object/deal': {
|
|
776
|
+
id: 'test.pipeline-sys:object/deal',
|
|
777
|
+
label: 'Deal',
|
|
778
|
+
ownerSystemId: 'test.pipeline-sys'
|
|
779
|
+
}
|
|
717
780
|
},
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
781
|
+
catalogTypes: {
|
|
782
|
+
'test.pipeline-sys:catalog/deal-pipeline': {
|
|
783
|
+
id: 'test.pipeline-sys:catalog/deal-pipeline',
|
|
784
|
+
label: 'Deal Pipeline',
|
|
785
|
+
kind: 'pipeline',
|
|
786
|
+
appliesTo: 'test.pipeline-sys:object/deal',
|
|
787
|
+
entries: {
|
|
788
|
+
'closed-won': { label: 'Closed Won', order: 10, semanticClass: 'closed_won' }
|
|
789
|
+
}
|
|
790
|
+
}
|
|
724
791
|
}
|
|
725
792
|
}
|
|
726
793
|
}
|
|
@@ -728,77 +795,124 @@ describe('organization graph', () => {
|
|
|
728
795
|
})
|
|
729
796
|
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
730
797
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
798
|
+
expect(graph.nodes.find((n) => n.id === 'ontology:test.pipeline-sys:catalog/deal-pipeline')).toMatchObject({
|
|
799
|
+
kind: 'ontology',
|
|
800
|
+
sourceId: 'test.pipeline-sys:catalog/deal-pipeline',
|
|
801
|
+
label: 'Deal Pipeline',
|
|
802
|
+
ontologyKind: 'catalog'
|
|
736
803
|
})
|
|
737
|
-
|
|
738
|
-
// Stage content-node emitted
|
|
739
|
-
expect(graph.nodes.find((n) => n.id === 'content-node:test.pipeline-sys:closed-won')).toMatchObject({
|
|
740
|
-
kind: 'content-node',
|
|
741
|
-
sourceId: 'test.pipeline-sys:closed-won',
|
|
742
|
-
label: 'Closed Won'
|
|
743
|
-
})
|
|
744
|
-
|
|
745
|
-
// contains: system spine → pipeline content-node
|
|
746
|
-
expect(
|
|
747
|
-
graph.edges.find(
|
|
748
|
-
(e) =>
|
|
749
|
-
e.kind === 'contains' &&
|
|
750
|
-
e.sourceId === 'system:test.pipeline-sys' &&
|
|
751
|
-
e.targetId === 'content-node:test.pipeline-sys:deal-pipeline'
|
|
752
|
-
)
|
|
753
|
-
).toBeDefined()
|
|
754
|
-
|
|
755
|
-
// contains: system spine → stage content-node
|
|
804
|
+
expect(graph.nodes.some((n) => n.id.startsWith('content-node:'))).toBe(false)
|
|
756
805
|
expect(
|
|
757
806
|
graph.edges.find(
|
|
758
807
|
(e) =>
|
|
759
|
-
e.kind === '
|
|
760
|
-
e.sourceId === '
|
|
761
|
-
e.targetId === '
|
|
808
|
+
e.kind === 'applies_to' &&
|
|
809
|
+
e.sourceId === 'ontology:test.pipeline-sys:catalog/deal-pipeline' &&
|
|
810
|
+
e.targetId === 'ontology:test.pipeline-sys:object/deal'
|
|
762
811
|
)
|
|
763
812
|
).toBeDefined()
|
|
764
813
|
})
|
|
765
814
|
|
|
766
|
-
it('projects
|
|
767
|
-
const
|
|
768
|
-
|
|
815
|
+
it('projects status-flow catalog entries through ontology rather than content parent chains', () => {
|
|
816
|
+
const model = resolveOrganizationModel({
|
|
817
|
+
systems: {
|
|
818
|
+
'test.status-sys': {
|
|
819
|
+
id: 'test.status-sys',
|
|
820
|
+
order: 10,
|
|
821
|
+
label: 'Status System',
|
|
822
|
+
enabled: true,
|
|
823
|
+
ontology: {
|
|
824
|
+
objectTypes: {
|
|
825
|
+
'test.status-sys:object/task': {
|
|
826
|
+
id: 'test.status-sys:object/task',
|
|
827
|
+
label: 'Task',
|
|
828
|
+
ownerSystemId: 'test.status-sys'
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
catalogTypes: {
|
|
832
|
+
'test.status-sys:catalog/task-status-flow': {
|
|
833
|
+
id: 'test.status-sys:catalog/task-status-flow',
|
|
834
|
+
label: 'Task Status Flow',
|
|
835
|
+
kind: 'status-flow',
|
|
836
|
+
appliesTo: 'test.status-sys:object/task',
|
|
837
|
+
entries: {
|
|
838
|
+
approved: { label: 'Approved', order: 10 },
|
|
839
|
+
rejected: { label: 'Rejected', order: 20 }
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
769
846
|
})
|
|
847
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
770
848
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const sample = Object.values(DEFAULT_ORGANIZATION_MODEL_ACTIONS)[0]!
|
|
775
|
-
const node = graph.nodes.find((n) => n.id === `action:${sample.id}`)
|
|
776
|
-
expect(node).toMatchObject({
|
|
777
|
-
kind: 'action',
|
|
778
|
-
sourceId: sample.id,
|
|
779
|
-
label: sample.label,
|
|
780
|
-
description: sample.description
|
|
849
|
+
expect(graph.nodes.find((n) => n.id === 'ontology:test.status-sys:catalog/task-status-flow')).toMatchObject({
|
|
850
|
+
kind: 'ontology',
|
|
851
|
+
label: 'Task Status Flow'
|
|
781
852
|
})
|
|
853
|
+
expect(graph.nodes.some((n) => n.id.startsWith('content-node:test.status-sys:'))).toBe(false)
|
|
854
|
+
})
|
|
782
855
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
856
|
+
it('projects nested system ontology catalogs with scoped ontology node ids', () => {
|
|
857
|
+
const model = resolveOrganizationModel({
|
|
858
|
+
systems: {
|
|
859
|
+
ops: {
|
|
860
|
+
id: 'ops',
|
|
861
|
+
order: 10,
|
|
862
|
+
label: 'Ops',
|
|
863
|
+
enabled: true,
|
|
864
|
+
systems: {
|
|
865
|
+
delivery: {
|
|
866
|
+
id: 'ops.delivery',
|
|
867
|
+
order: 20,
|
|
868
|
+
label: 'Delivery',
|
|
869
|
+
enabled: true,
|
|
870
|
+
ontology: {
|
|
871
|
+
objectTypes: {
|
|
872
|
+
'ops.delivery:object/list': {
|
|
873
|
+
id: 'ops.delivery:object/list',
|
|
874
|
+
label: 'List',
|
|
875
|
+
ownerSystemId: 'ops.delivery'
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
catalogTypes: {
|
|
879
|
+
'ops.delivery:catalog/onboarding-template': {
|
|
880
|
+
id: 'ops.delivery:catalog/onboarding-template',
|
|
881
|
+
label: 'Onboarding Template',
|
|
882
|
+
kind: 'template',
|
|
883
|
+
appliesTo: 'ops.delivery:object/list',
|
|
884
|
+
entries: {
|
|
885
|
+
default: { label: 'Default' }
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
})
|
|
895
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
791
896
|
|
|
792
|
-
expect(graph.nodes.find((n) => n.id ===
|
|
897
|
+
expect(graph.nodes.find((n) => n.id === 'ontology:ops.delivery:catalog/onboarding-template')).toMatchObject({
|
|
898
|
+
kind: 'ontology',
|
|
899
|
+
sourceId: 'ops.delivery:catalog/onboarding-template',
|
|
900
|
+
label: 'Onboarding Template'
|
|
901
|
+
})
|
|
902
|
+
expect(graph.nodes.some((n) => n.id.startsWith('content-node:ops.delivery:'))).toBe(false)
|
|
793
903
|
})
|
|
794
904
|
|
|
795
905
|
it('projects entity nodes with system contains edges and entity links', () => {
|
|
796
906
|
const graph = buildOrganizationGraph({
|
|
797
|
-
organizationModel: resolveOrganizationModel(
|
|
907
|
+
organizationModel: resolveOrganizationModel({
|
|
908
|
+
systems: TEST_SYSTEMS,
|
|
909
|
+
actions: { [TEST_ACTION.id]: TEST_ACTION },
|
|
910
|
+
entities: TEST_ENTITIES
|
|
911
|
+
})
|
|
798
912
|
})
|
|
799
913
|
|
|
800
914
|
const entityNodes = graph.nodes.filter((node) => node.kind === 'entity')
|
|
801
|
-
expect(entityNodes).toHaveLength(Object.keys(
|
|
915
|
+
expect(entityNodes).toHaveLength(Object.keys(TEST_ENTITIES).length)
|
|
802
916
|
expect(graph.nodes.find((node) => node.id === 'entity:crm.deal')).toMatchObject({
|
|
803
917
|
kind: 'entity',
|
|
804
918
|
sourceId: 'crm.deal',
|
|
@@ -819,7 +933,10 @@ describe('organization graph', () => {
|
|
|
819
933
|
|
|
820
934
|
it('projects affects edges from actions to entities', () => {
|
|
821
935
|
const model = resolveOrganizationModel({
|
|
936
|
+
systems: TEST_SYSTEMS,
|
|
937
|
+
entities: TEST_ENTITIES,
|
|
822
938
|
actions: {
|
|
939
|
+
[TEST_ACTION.id]: TEST_ACTION,
|
|
823
940
|
'crm.deal.update': {
|
|
824
941
|
id: 'crm.deal.update',
|
|
825
942
|
order: 10,
|
|
@@ -840,6 +957,8 @@ describe('organization graph', () => {
|
|
|
840
957
|
|
|
841
958
|
it('projects event nodes from workflow and agent resource emission traits', () => {
|
|
842
959
|
const model = resolveOrganizationModel({
|
|
960
|
+
systems: TEST_SYSTEMS,
|
|
961
|
+
actions: { [TEST_ACTION.id]: TEST_ACTION },
|
|
843
962
|
resources: {
|
|
844
963
|
'leadgen-reply-workflow': {
|
|
845
964
|
id: 'leadgen-reply-workflow',
|
|
@@ -892,79 +1011,11 @@ describe('organization graph', () => {
|
|
|
892
1011
|
expect(graph.nodes.some((node) => node.id.startsWith('event:crm-integration:'))).toBe(false)
|
|
893
1012
|
})
|
|
894
1013
|
|
|
895
|
-
// Wave 5: content-node parentContentId chain emits contains edges parent → child.
|
|
896
|
-
it('emits contains edges for parentContentId chains between content-nodes', () => {
|
|
897
|
-
const model = resolveOrganizationModel({
|
|
898
|
-
systems: {
|
|
899
|
-
'test.status-sys': {
|
|
900
|
-
id: 'test.status-sys',
|
|
901
|
-
order: 10,
|
|
902
|
-
label: 'Status System',
|
|
903
|
-
enabled: true,
|
|
904
|
-
content: {
|
|
905
|
-
'task-status-flow': {
|
|
906
|
-
kind: 'schema',
|
|
907
|
-
type: 'status-flow',
|
|
908
|
-
label: 'Task Status Flow'
|
|
909
|
-
},
|
|
910
|
-
'status-approved': {
|
|
911
|
-
kind: 'schema',
|
|
912
|
-
type: 'status',
|
|
913
|
-
label: 'Approved',
|
|
914
|
-
parentContentId: 'task-status-flow'
|
|
915
|
-
},
|
|
916
|
-
'status-rejected': {
|
|
917
|
-
kind: 'schema',
|
|
918
|
-
type: 'status',
|
|
919
|
-
label: 'Rejected',
|
|
920
|
-
parentContentId: 'task-status-flow'
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
})
|
|
926
|
-
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
927
|
-
|
|
928
|
-
// Parent content-node emitted
|
|
929
|
-
expect(graph.nodes.find((n) => n.id === 'content-node:test.status-sys:task-status-flow')).toMatchObject({
|
|
930
|
-
kind: 'content-node',
|
|
931
|
-
label: 'Task Status Flow'
|
|
932
|
-
})
|
|
933
|
-
|
|
934
|
-
// Child content-nodes emitted
|
|
935
|
-
expect(graph.nodes.find((n) => n.id === 'content-node:test.status-sys:status-approved')).toMatchObject({
|
|
936
|
-
kind: 'content-node',
|
|
937
|
-
label: 'Approved'
|
|
938
|
-
})
|
|
939
|
-
expect(graph.nodes.find((n) => n.id === 'content-node:test.status-sys:status-rejected')).toMatchObject({
|
|
940
|
-
kind: 'content-node',
|
|
941
|
-
label: 'Rejected'
|
|
942
|
-
})
|
|
943
|
-
|
|
944
|
-
// parentContentId chain: parent → approved
|
|
945
|
-
expect(
|
|
946
|
-
graph.edges.find(
|
|
947
|
-
(e) =>
|
|
948
|
-
e.kind === 'contains' &&
|
|
949
|
-
e.sourceId === 'content-node:test.status-sys:task-status-flow' &&
|
|
950
|
-
e.targetId === 'content-node:test.status-sys:status-approved'
|
|
951
|
-
)
|
|
952
|
-
).toBeDefined()
|
|
953
|
-
|
|
954
|
-
// parentContentId chain: parent → rejected
|
|
955
|
-
expect(
|
|
956
|
-
graph.edges.find(
|
|
957
|
-
(e) =>
|
|
958
|
-
e.kind === 'contains' &&
|
|
959
|
-
e.sourceId === 'content-node:test.status-sys:task-status-flow' &&
|
|
960
|
-
e.targetId === 'content-node:test.status-sys:status-rejected'
|
|
961
|
-
)
|
|
962
|
-
).toBeDefined()
|
|
963
|
-
})
|
|
964
1014
|
|
|
965
1015
|
it('links event-triggered policies to projected event nodes', () => {
|
|
966
|
-
const action =
|
|
1016
|
+
const action = TEST_ACTION
|
|
967
1017
|
const model = resolveOrganizationModel({
|
|
1018
|
+
...BASE_GRAPH_MODEL,
|
|
968
1019
|
resources: {
|
|
969
1020
|
'leadgen-reply-workflow': {
|
|
970
1021
|
id: 'leadgen-reply-workflow',
|
|
@@ -1008,7 +1059,7 @@ describe('organization graph', () => {
|
|
|
1008
1059
|
})
|
|
1009
1060
|
|
|
1010
1061
|
it('emits uses edges from systems to attached actions', () => {
|
|
1011
|
-
const graph = buildOrganizationGraph({ organizationModel: resolveOrganizationModel() })
|
|
1062
|
+
const graph = buildOrganizationGraph({ organizationModel: resolveOrganizationModel(BASE_GRAPH_MODEL) })
|
|
1012
1063
|
|
|
1013
1064
|
expect(
|
|
1014
1065
|
graph.edges.find(
|
|
@@ -1021,8 +1072,9 @@ describe('organization graph', () => {
|
|
|
1021
1072
|
})
|
|
1022
1073
|
|
|
1023
1074
|
it('derives resource membership and agent invocation edges from canonical OM fields', () => {
|
|
1024
|
-
const action =
|
|
1075
|
+
const action = TEST_ACTION
|
|
1025
1076
|
const model = resolveOrganizationModel({
|
|
1077
|
+
...BASE_GRAPH_MODEL,
|
|
1026
1078
|
resources: {
|
|
1027
1079
|
'ops-agent': {
|
|
1028
1080
|
id: 'ops-agent',
|
|
@@ -1075,104 +1127,13 @@ describe('organization graph', () => {
|
|
|
1075
1127
|
).toMatchObject({ label: 'script ops-script' })
|
|
1076
1128
|
})
|
|
1077
1129
|
|
|
1078
|
-
// Wave 5: pipeline content-node with data.entityId emits a references edge to the entity node.
|
|
1079
|
-
it('emits references edges from pipeline content-nodes to their target entity nodes', () => {
|
|
1080
|
-
const model = resolveOrganizationModel({
|
|
1081
|
-
systems: {
|
|
1082
|
-
'test.pipeline-ref': {
|
|
1083
|
-
id: 'test.pipeline-ref',
|
|
1084
|
-
order: 10,
|
|
1085
|
-
label: 'Pipeline Ref System',
|
|
1086
|
-
enabled: true,
|
|
1087
|
-
content: {
|
|
1088
|
-
'deal-pipeline': {
|
|
1089
|
-
kind: 'schema',
|
|
1090
|
-
type: 'pipeline',
|
|
1091
|
-
label: 'Deal Pipeline',
|
|
1092
|
-
data: { entityId: 'crm.deal' }
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
})
|
|
1098
|
-
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
1099
|
-
|
|
1100
|
-
// references: pipeline content-node → entity node
|
|
1101
|
-
expect(
|
|
1102
|
-
graph.edges.find(
|
|
1103
|
-
(e) =>
|
|
1104
|
-
e.kind === 'references' &&
|
|
1105
|
-
e.sourceId === 'content-node:test.pipeline-ref:deal-pipeline' &&
|
|
1106
|
-
e.targetId === 'entity:crm.deal'
|
|
1107
|
-
)
|
|
1108
|
-
).toMatchObject({ label: 'applies to entity' })
|
|
1109
|
-
})
|
|
1110
|
-
|
|
1111
|
-
// Wave 5: content-nodes across nested subsystems are projected correctly.
|
|
1112
|
-
it('projects content-nodes from nested subsystem paths with correct scoped node ids', () => {
|
|
1113
|
-
const model = resolveOrganizationModel({
|
|
1114
|
-
systems: {
|
|
1115
|
-
'ops.delivery': {
|
|
1116
|
-
id: 'ops.delivery',
|
|
1117
|
-
order: 10,
|
|
1118
|
-
label: 'Delivery',
|
|
1119
|
-
enabled: true,
|
|
1120
|
-
content: {
|
|
1121
|
-
'template-onboard': {
|
|
1122
|
-
kind: 'schema',
|
|
1123
|
-
type: 'template',
|
|
1124
|
-
label: 'Onboarding Template'
|
|
1125
|
-
},
|
|
1126
|
-
'step-intro': {
|
|
1127
|
-
kind: 'schema',
|
|
1128
|
-
type: 'template-step',
|
|
1129
|
-
label: 'Introduction Step',
|
|
1130
|
-
parentContentId: 'template-onboard'
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
})
|
|
1136
|
-
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
1137
|
-
|
|
1138
|
-
// Node ids use full dot-separated system path
|
|
1139
|
-
expect(graph.nodes.find((n) => n.id === 'content-node:ops.delivery:template-onboard')).toMatchObject({
|
|
1140
|
-
kind: 'content-node',
|
|
1141
|
-
sourceId: 'ops.delivery:template-onboard',
|
|
1142
|
-
label: 'Onboarding Template'
|
|
1143
|
-
})
|
|
1144
|
-
expect(graph.nodes.find((n) => n.id === 'content-node:ops.delivery:step-intro')).toMatchObject({
|
|
1145
|
-
kind: 'content-node',
|
|
1146
|
-
sourceId: 'ops.delivery:step-intro',
|
|
1147
|
-
label: 'Introduction Step'
|
|
1148
|
-
})
|
|
1149
|
-
|
|
1150
|
-
// contains: system spine (ops.delivery) → template content-node
|
|
1151
|
-
expect(
|
|
1152
|
-
graph.edges.find(
|
|
1153
|
-
(e) =>
|
|
1154
|
-
e.kind === 'contains' &&
|
|
1155
|
-
e.sourceId === 'system:ops.delivery' &&
|
|
1156
|
-
e.targetId === 'content-node:ops.delivery:template-onboard'
|
|
1157
|
-
)
|
|
1158
|
-
).toBeDefined()
|
|
1159
|
-
|
|
1160
|
-
// parentContentId chain: template → step
|
|
1161
|
-
expect(
|
|
1162
|
-
graph.edges.find(
|
|
1163
|
-
(e) =>
|
|
1164
|
-
e.kind === 'contains' &&
|
|
1165
|
-
e.sourceId === 'content-node:ops.delivery:template-onboard' &&
|
|
1166
|
-
e.targetId === 'content-node:ops.delivery:step-intro'
|
|
1167
|
-
)
|
|
1168
|
-
).toBeDefined()
|
|
1169
|
-
})
|
|
1170
1130
|
|
|
1171
1131
|
describe('policy edges in graph projection', () => {
|
|
1172
|
-
const action =
|
|
1132
|
+
const action = TEST_ACTION
|
|
1173
1133
|
|
|
1174
1134
|
it('event-triggered policy emits triggers edge from event node to policy node', () => {
|
|
1175
1135
|
const model = resolveOrganizationModel({
|
|
1136
|
+
...BASE_GRAPH_MODEL,
|
|
1176
1137
|
resources: {
|
|
1177
1138
|
'reply-workflow': {
|
|
1178
1139
|
id: 'reply-workflow',
|
|
@@ -1207,6 +1168,7 @@ describe('organization graph', () => {
|
|
|
1207
1168
|
|
|
1208
1169
|
it('action-invocation-triggered policy emits triggers edge from action node to policy node', () => {
|
|
1209
1170
|
const model = resolveOrganizationModel({
|
|
1171
|
+
...BASE_GRAPH_MODEL,
|
|
1210
1172
|
policies: {
|
|
1211
1173
|
'policy.action.gate': {
|
|
1212
1174
|
id: 'policy.action.gate',
|
|
@@ -1231,6 +1193,7 @@ describe('organization graph', () => {
|
|
|
1231
1193
|
|
|
1232
1194
|
it('invoke-action effect emits effects edge from policy to target action', () => {
|
|
1233
1195
|
const model = resolveOrganizationModel({
|
|
1196
|
+
...BASE_GRAPH_MODEL,
|
|
1234
1197
|
policies: {
|
|
1235
1198
|
'policy.invoke': {
|
|
1236
1199
|
id: 'policy.invoke',
|
|
@@ -1319,6 +1282,8 @@ describe('organization graph', () => {
|
|
|
1319
1282
|
|
|
1320
1283
|
it('appliesTo.systemIds emits applies_to edges to each system', () => {
|
|
1321
1284
|
const model = resolveOrganizationModel({
|
|
1285
|
+
systems: TEST_SYSTEMS,
|
|
1286
|
+
actions: { [TEST_ACTION.id]: TEST_ACTION },
|
|
1322
1287
|
policies: {
|
|
1323
1288
|
'policy.sys-scope': {
|
|
1324
1289
|
id: 'policy.sys-scope',
|
|
@@ -1352,6 +1317,7 @@ describe('organization graph', () => {
|
|
|
1352
1317
|
|
|
1353
1318
|
it('appliesTo.actionIds emits applies_to edges to each action', () => {
|
|
1354
1319
|
const model = resolveOrganizationModel({
|
|
1320
|
+
...BASE_GRAPH_MODEL,
|
|
1355
1321
|
policies: {
|
|
1356
1322
|
'policy.action-scope': {
|
|
1357
1323
|
id: 'policy.action-scope',
|
|
@@ -1377,6 +1343,8 @@ describe('organization graph', () => {
|
|
|
1377
1343
|
|
|
1378
1344
|
it('appliesTo.resourceIds emits applies_to edges to each resource', () => {
|
|
1379
1345
|
const model = resolveOrganizationModel({
|
|
1346
|
+
systems: TEST_SYSTEMS,
|
|
1347
|
+
actions: { [TEST_ACTION.id]: TEST_ACTION },
|
|
1380
1348
|
resources: {
|
|
1381
1349
|
'scoped-workflow': {
|
|
1382
1350
|
id: 'scoped-workflow',
|