@elevasis/core 0.1.0 → 0.2.1
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.js +195 -3
- package/dist/organization-model/index.js +195 -3
- package/package.json +1 -1
- package/src/README.md +24 -17
- package/src/__tests__/template-foundations-compatibility.test.ts +95 -14
- package/src/auth/multi-tenancy/types.ts +2 -1
- package/src/execution/engine/__tests__/fixtures/test-agents.ts +4 -4
- package/src/execution/engine/index.ts +5 -19
- package/src/execution/engine/tools/platform/index.ts +9 -33
- package/src/execution/engine/tools/registry.ts +109 -2
- package/src/execution/engine/tools/tool-maps.ts +88 -0
- package/src/organization-model/README.md +19 -4
- package/src/organization-model/__tests__/graph.test.ts +612 -0
- package/src/organization-model/__tests__/resolve.test.ts +208 -0
- package/src/organization-model/defaults.ts +1 -1
- package/src/organization-model/organization-graph.mdx +262 -0
- package/src/organization-model/organization-model.mdx +257 -0
- package/src/organization-model/resolve.ts +26 -2
- package/src/organization-model/schema.ts +203 -1
- package/src/platform/constants/versions.ts +1 -1
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +24 -0
- package/src/platform/registry/__tests__/resource-registry.test.ts +63 -0
- package/src/platform/registry/resource-registry.ts +98 -10
- package/src/projects/api-schemas.ts +2 -1
- package/src/reference/_generated/contracts.md +1044 -0
- package/src/reference/glossary.md +88 -0
- package/src/server.ts +2 -3
- package/src/execution/engine/tools/platform/resource-invocation/__tests__/edge-cases.test.ts +0 -507
- package/src/execution/engine/tools/platform/resource-invocation/__tests__/resource-invocation-service.test.ts +0 -500
- package/src/execution/engine/tools/platform/resource-invocation/__tests__/tool.test.ts +0 -555
- package/src/execution/engine/tools/platform/resource-invocation/dynamic-tool.ts +0 -94
- package/src/execution/engine/tools/platform/resource-invocation/index.ts +0 -14
- package/src/execution/engine/tools/platform/resource-invocation/resource-invocation-service.ts +0 -147
- package/src/execution/engine/tools/platform/resource-invocation/tool.ts +0 -115
- package/src/execution/engine/tools/platform/resource-invocation/types.ts +0 -31
|
@@ -80,9 +80,621 @@ describe('organization-graph', () => {
|
|
|
80
80
|
)
|
|
81
81
|
})
|
|
82
82
|
|
|
83
|
+
describe('titleCase fallback for feature labels', () => {
|
|
84
|
+
it('uses titleCase of the feature key when no label is configured', () => {
|
|
85
|
+
const model = resolveOrganizationModel(
|
|
86
|
+
defineOrganizationModel({
|
|
87
|
+
branding: { organizationName: 'Acme', productName: 'Acme OS', shortName: 'Acme' }
|
|
88
|
+
})
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
92
|
+
|
|
93
|
+
// All feature keys are single lowercase words with no label overrides by default.
|
|
94
|
+
// titleCase('acquisition') => 'Acquisition', etc.
|
|
95
|
+
const featureKeys = ['acquisition', 'delivery', 'operations', 'monitoring', 'settings', 'seo', 'calibration']
|
|
96
|
+
for (const key of featureKeys) {
|
|
97
|
+
const node = graph.nodes.find((n) => n.id === `feature:${key}`)
|
|
98
|
+
expect(node, `feature node for '${key}' should exist`).toBeDefined()
|
|
99
|
+
// No label override is set, so the label must be the titleCase of the key.
|
|
100
|
+
const expected = key.charAt(0).toUpperCase() + key.slice(1)
|
|
101
|
+
expect(node?.label).toBe(expected)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('uses the explicit label from features.labels when one is configured', () => {
|
|
106
|
+
const model = resolveOrganizationModel(
|
|
107
|
+
defineOrganizationModel({
|
|
108
|
+
branding: { organizationName: 'Acme', productName: 'Acme OS', shortName: 'Acme' },
|
|
109
|
+
features: {
|
|
110
|
+
labels: { acquisition: 'Lead Generation' }
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
116
|
+
|
|
117
|
+
const node = graph.nodes.find((n) => n.id === 'feature:acquisition')
|
|
118
|
+
expect(node?.label).toBe('Lead Generation')
|
|
119
|
+
|
|
120
|
+
// Other features without explicit labels still fall back to titleCase.
|
|
121
|
+
const deliveryNode = graph.nodes.find((n) => n.id === 'feature:delivery')
|
|
122
|
+
expect(deliveryNode?.label).toBe('Delivery')
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('resource node upsert merging', () => {
|
|
127
|
+
it('keeps resourceMappings label and merges description from commandViewData', () => {
|
|
128
|
+
// Surface and domain are kept minimal (no cross-references) so that
|
|
129
|
+
// OrganizationModelSchema bidirectional integrity checks pass. The test
|
|
130
|
+
// is only exercising resource node merging, not surface/domain linkage.
|
|
131
|
+
const model = resolveOrganizationModel(
|
|
132
|
+
defineOrganizationModel({
|
|
133
|
+
domains: [
|
|
134
|
+
{
|
|
135
|
+
id: 'operations',
|
|
136
|
+
label: 'Operations',
|
|
137
|
+
color: 'violet',
|
|
138
|
+
entityIds: [],
|
|
139
|
+
surfaceIds: ['operations.command-view'],
|
|
140
|
+
resourceIds: ['workflow-order'],
|
|
141
|
+
capabilityIds: []
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
navigation: {
|
|
145
|
+
defaultSurfaceId: 'operations.command-view',
|
|
146
|
+
surfaces: [
|
|
147
|
+
{
|
|
148
|
+
id: 'operations.command-view',
|
|
149
|
+
label: 'Command View',
|
|
150
|
+
path: '/operations/command-view',
|
|
151
|
+
surfaceType: 'graph',
|
|
152
|
+
featureKey: 'operations',
|
|
153
|
+
domainIds: ['operations'],
|
|
154
|
+
entityIds: [],
|
|
155
|
+
resourceIds: ['workflow-order'],
|
|
156
|
+
capabilityIds: []
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
groups: [
|
|
160
|
+
{
|
|
161
|
+
id: 'primary-operations',
|
|
162
|
+
label: 'Operations',
|
|
163
|
+
placement: 'primary',
|
|
164
|
+
surfaceIds: ['operations.command-view']
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
resourceMappings: [
|
|
169
|
+
{
|
|
170
|
+
id: 'workflow-order-model',
|
|
171
|
+
label: 'Order Workflow',
|
|
172
|
+
resourceId: 'workflow-order',
|
|
173
|
+
resourceType: 'workflow',
|
|
174
|
+
domainIds: ['operations'],
|
|
175
|
+
entityIds: [],
|
|
176
|
+
surfaceIds: ['operations.command-view'],
|
|
177
|
+
capabilityIds: []
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
})
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
const graph = buildOrganizationGraph({
|
|
184
|
+
organizationModel: model,
|
|
185
|
+
commandViewData: {
|
|
186
|
+
workflows: [
|
|
187
|
+
{
|
|
188
|
+
resourceId: 'workflow-order',
|
|
189
|
+
name: 'Order Workflow Runtime',
|
|
190
|
+
description: 'Runtime description from commandViewData',
|
|
191
|
+
version: '1.0.0',
|
|
192
|
+
type: 'workflow',
|
|
193
|
+
status: 'prod',
|
|
194
|
+
domains: ['operations'],
|
|
195
|
+
stepCount: 2,
|
|
196
|
+
entryPoint: 'start'
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
agents: [],
|
|
200
|
+
triggers: [],
|
|
201
|
+
integrations: [],
|
|
202
|
+
externalResources: [],
|
|
203
|
+
humanCheckpoints: [],
|
|
204
|
+
edges: []
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const resourceNodes = graph.nodes.filter((n) => n.id === 'resource:workflow-order')
|
|
209
|
+
expect(resourceNodes).toHaveLength(1)
|
|
210
|
+
|
|
211
|
+
const node = resourceNodes[0]
|
|
212
|
+
// Label from resourceMappings is a real label (not just the sourceId), so it is preserved.
|
|
213
|
+
expect(node.label).toBe('Order Workflow')
|
|
214
|
+
// Description was absent in resourceMappings, so it is filled in from commandViewData.
|
|
215
|
+
expect(node.description).toBe('Runtime description from commandViewData')
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('preserves existing non-default label even when commandViewData provides a different name', () => {
|
|
219
|
+
const model = resolveOrganizationModel(
|
|
220
|
+
defineOrganizationModel({
|
|
221
|
+
domains: [
|
|
222
|
+
{
|
|
223
|
+
id: 'ops',
|
|
224
|
+
label: 'Ops',
|
|
225
|
+
color: 'blue',
|
|
226
|
+
entityIds: [],
|
|
227
|
+
surfaceIds: ['ops.view'],
|
|
228
|
+
resourceIds: ['res-a'],
|
|
229
|
+
capabilityIds: []
|
|
230
|
+
}
|
|
231
|
+
],
|
|
232
|
+
navigation: {
|
|
233
|
+
defaultSurfaceId: 'ops.view',
|
|
234
|
+
surfaces: [
|
|
235
|
+
{
|
|
236
|
+
id: 'ops.view',
|
|
237
|
+
label: 'Ops View',
|
|
238
|
+
path: '/ops/view',
|
|
239
|
+
surfaceType: 'graph',
|
|
240
|
+
featureKey: 'operations',
|
|
241
|
+
domainIds: ['ops'],
|
|
242
|
+
entityIds: [],
|
|
243
|
+
resourceIds: ['res-a'],
|
|
244
|
+
capabilityIds: []
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
groups: [{ id: 'g', label: 'Ops', placement: 'primary', surfaceIds: ['ops.view'] }]
|
|
248
|
+
},
|
|
249
|
+
resourceMappings: [
|
|
250
|
+
{
|
|
251
|
+
id: 'res-a-model',
|
|
252
|
+
label: 'My Custom Label',
|
|
253
|
+
resourceId: 'res-a',
|
|
254
|
+
resourceType: 'workflow',
|
|
255
|
+
domainIds: ['ops'],
|
|
256
|
+
entityIds: [],
|
|
257
|
+
surfaceIds: ['ops.view'],
|
|
258
|
+
capabilityIds: []
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
})
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
const graph = buildOrganizationGraph({
|
|
265
|
+
organizationModel: model,
|
|
266
|
+
commandViewData: {
|
|
267
|
+
workflows: [
|
|
268
|
+
{
|
|
269
|
+
resourceId: 'res-a',
|
|
270
|
+
name: 'Runtime Name Should Not Override',
|
|
271
|
+
description: 'desc',
|
|
272
|
+
version: '1.0.0',
|
|
273
|
+
type: 'workflow',
|
|
274
|
+
status: 'prod',
|
|
275
|
+
domains: ['ops'],
|
|
276
|
+
stepCount: 1,
|
|
277
|
+
entryPoint: 'start'
|
|
278
|
+
}
|
|
279
|
+
],
|
|
280
|
+
agents: [],
|
|
281
|
+
triggers: [],
|
|
282
|
+
integrations: [],
|
|
283
|
+
externalResources: [],
|
|
284
|
+
humanCheckpoints: [],
|
|
285
|
+
edges: []
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
const node = graph.nodes.find((n) => n.id === 'resource:res-a')
|
|
290
|
+
expect(node?.label).toBe('My Custom Label')
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
describe('de-duplication of graph elements', () => {
|
|
295
|
+
it('creates only one domain node when multiple surfaces reference the same domain', () => {
|
|
296
|
+
const model = resolveOrganizationModel(
|
|
297
|
+
defineOrganizationModel({
|
|
298
|
+
domains: [
|
|
299
|
+
{
|
|
300
|
+
id: 'crm',
|
|
301
|
+
label: 'CRM',
|
|
302
|
+
color: 'blue',
|
|
303
|
+
entityIds: [],
|
|
304
|
+
surfaceIds: ['crm.pipeline', 'crm.accounts'],
|
|
305
|
+
resourceIds: [],
|
|
306
|
+
capabilityIds: []
|
|
307
|
+
}
|
|
308
|
+
],
|
|
309
|
+
navigation: {
|
|
310
|
+
defaultSurfaceId: 'crm.pipeline',
|
|
311
|
+
surfaces: [
|
|
312
|
+
{
|
|
313
|
+
id: 'crm.pipeline',
|
|
314
|
+
label: 'Pipeline',
|
|
315
|
+
path: '/crm/pipeline',
|
|
316
|
+
surfaceType: 'graph',
|
|
317
|
+
featureKey: 'acquisition',
|
|
318
|
+
domainIds: ['crm'],
|
|
319
|
+
entityIds: [],
|
|
320
|
+
resourceIds: [],
|
|
321
|
+
capabilityIds: []
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: 'crm.accounts',
|
|
325
|
+
label: 'Accounts',
|
|
326
|
+
path: '/crm/accounts',
|
|
327
|
+
surfaceType: 'list',
|
|
328
|
+
featureKey: 'acquisition',
|
|
329
|
+
domainIds: ['crm'],
|
|
330
|
+
entityIds: [],
|
|
331
|
+
resourceIds: [],
|
|
332
|
+
capabilityIds: []
|
|
333
|
+
}
|
|
334
|
+
],
|
|
335
|
+
groups: [{ id: 'g', label: 'CRM', placement: 'primary', surfaceIds: ['crm.pipeline', 'crm.accounts'] }]
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
341
|
+
|
|
342
|
+
const domainNodes = graph.nodes.filter((n) => n.id === 'domain:crm')
|
|
343
|
+
expect(domainNodes).toHaveLength(1)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('creates only one entity node when the same entity appears in both a domain and a surface', () => {
|
|
347
|
+
const model = resolveOrganizationModel(
|
|
348
|
+
defineOrganizationModel({
|
|
349
|
+
domains: [
|
|
350
|
+
{
|
|
351
|
+
id: 'crm',
|
|
352
|
+
label: 'CRM',
|
|
353
|
+
color: 'blue',
|
|
354
|
+
entityIds: ['crm.deal'],
|
|
355
|
+
surfaceIds: ['crm.pipeline'],
|
|
356
|
+
resourceIds: [],
|
|
357
|
+
capabilityIds: []
|
|
358
|
+
}
|
|
359
|
+
],
|
|
360
|
+
navigation: {
|
|
361
|
+
defaultSurfaceId: 'crm.pipeline',
|
|
362
|
+
surfaces: [
|
|
363
|
+
{
|
|
364
|
+
id: 'crm.pipeline',
|
|
365
|
+
label: 'Pipeline',
|
|
366
|
+
path: '/crm/pipeline',
|
|
367
|
+
surfaceType: 'graph',
|
|
368
|
+
featureKey: 'acquisition',
|
|
369
|
+
domainIds: ['crm'],
|
|
370
|
+
entityIds: ['crm.deal'],
|
|
371
|
+
resourceIds: [],
|
|
372
|
+
capabilityIds: []
|
|
373
|
+
}
|
|
374
|
+
],
|
|
375
|
+
groups: [{ id: 'g', label: 'CRM', placement: 'primary', surfaceIds: ['crm.pipeline'] }]
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
381
|
+
|
|
382
|
+
const entityNodes = graph.nodes.filter((n) => n.id === 'entity:crm.deal')
|
|
383
|
+
expect(entityNodes).toHaveLength(1)
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it('creates only one resource node when the same resource appears in resourceMappings and commandViewData', () => {
|
|
387
|
+
const model = resolveOrganizationModel(
|
|
388
|
+
defineOrganizationModel({
|
|
389
|
+
domains: [
|
|
390
|
+
{
|
|
391
|
+
id: 'ops',
|
|
392
|
+
label: 'Ops',
|
|
393
|
+
color: 'violet',
|
|
394
|
+
entityIds: [],
|
|
395
|
+
surfaceIds: ['ops.view'],
|
|
396
|
+
resourceIds: ['wf-shared'],
|
|
397
|
+
capabilityIds: []
|
|
398
|
+
}
|
|
399
|
+
],
|
|
400
|
+
navigation: {
|
|
401
|
+
defaultSurfaceId: 'ops.view',
|
|
402
|
+
surfaces: [
|
|
403
|
+
{
|
|
404
|
+
id: 'ops.view',
|
|
405
|
+
label: 'Ops View',
|
|
406
|
+
path: '/ops/view',
|
|
407
|
+
surfaceType: 'graph',
|
|
408
|
+
featureKey: 'operations',
|
|
409
|
+
domainIds: ['ops'],
|
|
410
|
+
entityIds: [],
|
|
411
|
+
resourceIds: ['wf-shared'],
|
|
412
|
+
capabilityIds: []
|
|
413
|
+
}
|
|
414
|
+
],
|
|
415
|
+
groups: [{ id: 'g', label: 'Ops', placement: 'primary', surfaceIds: ['ops.view'] }]
|
|
416
|
+
},
|
|
417
|
+
resourceMappings: [
|
|
418
|
+
{
|
|
419
|
+
id: 'wf-shared-model',
|
|
420
|
+
label: 'Shared Workflow',
|
|
421
|
+
resourceId: 'wf-shared',
|
|
422
|
+
resourceType: 'workflow',
|
|
423
|
+
domainIds: ['ops'],
|
|
424
|
+
entityIds: [],
|
|
425
|
+
surfaceIds: ['ops.view'],
|
|
426
|
+
capabilityIds: []
|
|
427
|
+
}
|
|
428
|
+
]
|
|
429
|
+
})
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
const graph = buildOrganizationGraph({
|
|
433
|
+
organizationModel: model,
|
|
434
|
+
commandViewData: {
|
|
435
|
+
workflows: [
|
|
436
|
+
{
|
|
437
|
+
resourceId: 'wf-shared',
|
|
438
|
+
name: 'Shared Workflow Runtime',
|
|
439
|
+
description: 'desc',
|
|
440
|
+
version: '1.0.0',
|
|
441
|
+
type: 'workflow',
|
|
442
|
+
status: 'prod',
|
|
443
|
+
domains: ['ops'],
|
|
444
|
+
stepCount: 1,
|
|
445
|
+
entryPoint: 'start'
|
|
446
|
+
}
|
|
447
|
+
],
|
|
448
|
+
agents: [],
|
|
449
|
+
triggers: [],
|
|
450
|
+
integrations: [],
|
|
451
|
+
externalResources: [],
|
|
452
|
+
humanCheckpoints: [],
|
|
453
|
+
edges: []
|
|
454
|
+
}
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
const resourceNodes = graph.nodes.filter((n) => n.id === 'resource:wf-shared')
|
|
458
|
+
expect(resourceNodes).toHaveLength(1)
|
|
459
|
+
})
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
describe('commandViewData edge relationships', () => {
|
|
463
|
+
it('creates a stub resource node with label === resourceId for edges referencing unknown resources', () => {
|
|
464
|
+
const model = resolveOrganizationModel(
|
|
465
|
+
defineOrganizationModel({
|
|
466
|
+
domains: [
|
|
467
|
+
{
|
|
468
|
+
id: 'ops',
|
|
469
|
+
label: 'Ops',
|
|
470
|
+
color: 'violet',
|
|
471
|
+
entityIds: [],
|
|
472
|
+
surfaceIds: [],
|
|
473
|
+
resourceIds: [],
|
|
474
|
+
capabilityIds: []
|
|
475
|
+
}
|
|
476
|
+
],
|
|
477
|
+
navigation: {
|
|
478
|
+
defaultSurfaceId: 'ops.view',
|
|
479
|
+
surfaces: [
|
|
480
|
+
{
|
|
481
|
+
id: 'ops.view',
|
|
482
|
+
label: 'Ops View',
|
|
483
|
+
path: '/ops/view',
|
|
484
|
+
surfaceType: 'graph',
|
|
485
|
+
featureKey: 'operations',
|
|
486
|
+
domainIds: [],
|
|
487
|
+
entityIds: [],
|
|
488
|
+
resourceIds: [],
|
|
489
|
+
capabilityIds: []
|
|
490
|
+
}
|
|
491
|
+
],
|
|
492
|
+
groups: [{ id: 'g', label: 'Ops', placement: 'primary', surfaceIds: ['ops.view'] }]
|
|
493
|
+
}
|
|
494
|
+
})
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
// Both source and target in the edge are NOT listed in any workflow/agent/etc. array.
|
|
498
|
+
const graph = buildOrganizationGraph({
|
|
499
|
+
organizationModel: model,
|
|
500
|
+
commandViewData: {
|
|
501
|
+
workflows: [],
|
|
502
|
+
agents: [],
|
|
503
|
+
triggers: [],
|
|
504
|
+
integrations: [],
|
|
505
|
+
externalResources: [],
|
|
506
|
+
humanCheckpoints: [],
|
|
507
|
+
edges: [
|
|
508
|
+
{
|
|
509
|
+
id: 'edge-unknown',
|
|
510
|
+
source: 'unknown-source',
|
|
511
|
+
target: 'unknown-target',
|
|
512
|
+
relationship: 'triggers'
|
|
513
|
+
}
|
|
514
|
+
]
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
const sourceNode = graph.nodes.find((n) => n.id === 'resource:unknown-source')
|
|
519
|
+
expect(sourceNode).toBeDefined()
|
|
520
|
+
expect(sourceNode?.label).toBe('unknown-source')
|
|
521
|
+
expect(sourceNode?.sourceId).toBe('unknown-source')
|
|
522
|
+
|
|
523
|
+
const targetNode = graph.nodes.find((n) => n.id === 'resource:unknown-target')
|
|
524
|
+
expect(targetNode).toBeDefined()
|
|
525
|
+
expect(targetNode?.label).toBe('unknown-target')
|
|
526
|
+
expect(targetNode?.sourceId).toBe('unknown-target')
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
it('normalizes humanCheckpoints type to human_checkpoint on resource nodes', () => {
|
|
530
|
+
const model = resolveOrganizationModel(
|
|
531
|
+
defineOrganizationModel({
|
|
532
|
+
domains: [
|
|
533
|
+
{
|
|
534
|
+
id: 'ops',
|
|
535
|
+
label: 'Ops',
|
|
536
|
+
color: 'violet',
|
|
537
|
+
entityIds: [],
|
|
538
|
+
surfaceIds: [],
|
|
539
|
+
resourceIds: [],
|
|
540
|
+
capabilityIds: []
|
|
541
|
+
}
|
|
542
|
+
],
|
|
543
|
+
navigation: {
|
|
544
|
+
defaultSurfaceId: 'ops.view',
|
|
545
|
+
surfaces: [
|
|
546
|
+
{
|
|
547
|
+
id: 'ops.view',
|
|
548
|
+
label: 'Ops View',
|
|
549
|
+
path: '/ops/view',
|
|
550
|
+
surfaceType: 'graph',
|
|
551
|
+
featureKey: 'operations',
|
|
552
|
+
domainIds: [],
|
|
553
|
+
entityIds: [],
|
|
554
|
+
resourceIds: [],
|
|
555
|
+
capabilityIds: []
|
|
556
|
+
}
|
|
557
|
+
],
|
|
558
|
+
groups: [{ id: 'g', label: 'Ops', placement: 'primary', surfaceIds: ['ops.view'] }]
|
|
559
|
+
}
|
|
560
|
+
})
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
const graph = buildOrganizationGraph({
|
|
564
|
+
organizationModel: model,
|
|
565
|
+
commandViewData: {
|
|
566
|
+
workflows: [],
|
|
567
|
+
agents: [],
|
|
568
|
+
triggers: [],
|
|
569
|
+
integrations: [],
|
|
570
|
+
externalResources: [],
|
|
571
|
+
humanCheckpoints: [
|
|
572
|
+
{
|
|
573
|
+
resourceId: 'approval-step',
|
|
574
|
+
name: 'Approval Step',
|
|
575
|
+
description: 'Human review gate',
|
|
576
|
+
version: '1.0.0',
|
|
577
|
+
type: 'human',
|
|
578
|
+
status: 'prod',
|
|
579
|
+
domains: ['ops']
|
|
580
|
+
}
|
|
581
|
+
],
|
|
582
|
+
edges: []
|
|
583
|
+
}
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
const node = graph.nodes.find((n) => n.id === 'resource:approval-step')
|
|
587
|
+
expect(node).toBeDefined()
|
|
588
|
+
expect(node?.resourceType).toBe('human_checkpoint')
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
it('creates a references edge with label and relationshipType for commandViewData edges', () => {
|
|
592
|
+
const model = resolveOrganizationModel(
|
|
593
|
+
defineOrganizationModel({
|
|
594
|
+
domains: [
|
|
595
|
+
{
|
|
596
|
+
id: 'ops',
|
|
597
|
+
label: 'Ops',
|
|
598
|
+
color: 'violet',
|
|
599
|
+
entityIds: [],
|
|
600
|
+
surfaceIds: [],
|
|
601
|
+
resourceIds: [],
|
|
602
|
+
capabilityIds: []
|
|
603
|
+
}
|
|
604
|
+
],
|
|
605
|
+
navigation: {
|
|
606
|
+
defaultSurfaceId: 'ops.view',
|
|
607
|
+
surfaces: [
|
|
608
|
+
{
|
|
609
|
+
id: 'ops.view',
|
|
610
|
+
label: 'Ops View',
|
|
611
|
+
path: '/ops/view',
|
|
612
|
+
surfaceType: 'graph',
|
|
613
|
+
featureKey: 'operations',
|
|
614
|
+
domainIds: [],
|
|
615
|
+
entityIds: [],
|
|
616
|
+
resourceIds: [],
|
|
617
|
+
capabilityIds: []
|
|
618
|
+
}
|
|
619
|
+
],
|
|
620
|
+
groups: [{ id: 'g', label: 'Ops', placement: 'primary', surfaceIds: ['ops.view'] }]
|
|
621
|
+
}
|
|
622
|
+
})
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
const graph = buildOrganizationGraph({
|
|
626
|
+
organizationModel: model,
|
|
627
|
+
commandViewData: {
|
|
628
|
+
workflows: [],
|
|
629
|
+
agents: [],
|
|
630
|
+
triggers: [],
|
|
631
|
+
integrations: [],
|
|
632
|
+
externalResources: [],
|
|
633
|
+
humanCheckpoints: [],
|
|
634
|
+
edges: [
|
|
635
|
+
{
|
|
636
|
+
id: 'edge-triggers',
|
|
637
|
+
source: 'svc-a',
|
|
638
|
+
target: 'svc-b',
|
|
639
|
+
relationship: 'triggers'
|
|
640
|
+
}
|
|
641
|
+
]
|
|
642
|
+
}
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
expect(graph.edges).toEqual(
|
|
646
|
+
expect.arrayContaining([
|
|
647
|
+
expect.objectContaining({
|
|
648
|
+
kind: 'references',
|
|
649
|
+
sourceId: 'resource:svc-a',
|
|
650
|
+
targetId: 'resource:svc-b',
|
|
651
|
+
label: 'triggers',
|
|
652
|
+
relationshipType: 'triggers'
|
|
653
|
+
})
|
|
654
|
+
])
|
|
655
|
+
)
|
|
656
|
+
})
|
|
657
|
+
})
|
|
658
|
+
|
|
83
659
|
it('bridges command view topology into resource nodes and labeled relationship edges', () => {
|
|
84
660
|
const model = resolveOrganizationModel(
|
|
85
661
|
defineOrganizationModel({
|
|
662
|
+
domains: [
|
|
663
|
+
{
|
|
664
|
+
id: 'operations',
|
|
665
|
+
label: 'Operations',
|
|
666
|
+
description: 'Operational resources, topology, and orchestration visibility',
|
|
667
|
+
color: 'violet',
|
|
668
|
+
entityIds: [],
|
|
669
|
+
surfaceIds: ['operations.command-view'],
|
|
670
|
+
resourceIds: ['workflow-order'],
|
|
671
|
+
capabilityIds: ['operations.command-view']
|
|
672
|
+
}
|
|
673
|
+
],
|
|
674
|
+
navigation: {
|
|
675
|
+
defaultSurfaceId: 'operations.command-view',
|
|
676
|
+
surfaces: [
|
|
677
|
+
{
|
|
678
|
+
id: 'operations.command-view',
|
|
679
|
+
label: 'Command View',
|
|
680
|
+
path: '/operations/command-view',
|
|
681
|
+
surfaceType: 'graph',
|
|
682
|
+
featureKey: 'operations',
|
|
683
|
+
domainIds: ['operations'],
|
|
684
|
+
entityIds: [],
|
|
685
|
+
resourceIds: ['workflow-order'],
|
|
686
|
+
capabilityIds: ['operations.command-view']
|
|
687
|
+
}
|
|
688
|
+
],
|
|
689
|
+
groups: [
|
|
690
|
+
{
|
|
691
|
+
id: 'primary-operations',
|
|
692
|
+
label: 'Operations',
|
|
693
|
+
placement: 'primary',
|
|
694
|
+
surfaceIds: ['operations.command-view']
|
|
695
|
+
}
|
|
696
|
+
]
|
|
697
|
+
},
|
|
86
698
|
resourceMappings: [
|
|
87
699
|
{
|
|
88
700
|
id: 'workflow-order-model',
|