@elevasis/core 0.10.0 → 0.11.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.
Files changed (58) hide show
  1. package/dist/index.d.ts +69 -159
  2. package/dist/index.js +324 -613
  3. package/dist/organization-model/index.d.ts +69 -159
  4. package/dist/organization-model/index.js +324 -613
  5. package/dist/test-utils/index.d.ts +192 -45
  6. package/dist/test-utils/index.js +260 -600
  7. package/package.json +1 -1
  8. package/src/__tests__/template-core-compatibility.test.ts +73 -91
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +94 -182
  10. package/src/auth/multi-tenancy/index.ts +20 -17
  11. package/src/auth/multi-tenancy/memberships/api-schemas.ts +142 -126
  12. package/src/auth/multi-tenancy/memberships/index.ts +26 -22
  13. package/src/auth/multi-tenancy/permissions.test.ts +42 -0
  14. package/src/auth/multi-tenancy/permissions.ts +104 -0
  15. package/src/organization-model/README.md +102 -97
  16. package/src/organization-model/__tests__/defaults.test.ts +19 -6
  17. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +24 -93
  18. package/src/organization-model/__tests__/graph.test.ts +82 -894
  19. package/src/organization-model/__tests__/resolve.test.ts +59 -690
  20. package/src/organization-model/__tests__/schema.test.ts +83 -407
  21. package/src/organization-model/contracts.ts +4 -3
  22. package/src/organization-model/defaults.ts +277 -141
  23. package/src/organization-model/domains/features.ts +31 -22
  24. package/src/organization-model/domains/navigation.ts +27 -20
  25. package/src/organization-model/foundation.ts +42 -54
  26. package/src/organization-model/graph/build.ts +42 -217
  27. package/src/organization-model/graph/index.ts +4 -4
  28. package/src/organization-model/graph/link.ts +10 -0
  29. package/src/organization-model/graph/schema.ts +21 -16
  30. package/src/organization-model/graph/types.ts +10 -10
  31. package/src/organization-model/helpers.ts +74 -0
  32. package/src/organization-model/index.ts +7 -7
  33. package/src/organization-model/organization-graph.mdx +89 -272
  34. package/src/organization-model/organization-model.mdx +152 -320
  35. package/src/organization-model/published.ts +20 -19
  36. package/src/organization-model/resolve.ts +8 -33
  37. package/src/organization-model/schema.ts +63 -205
  38. package/src/organization-model/types.ts +12 -11
  39. package/src/platform/constants/versions.ts +3 -3
  40. package/src/platform/registry/__tests__/command-view.test.ts +6 -5
  41. package/src/platform/registry/__tests__/resource-link.test.ts +30 -0
  42. package/src/platform/registry/__tests__/resource-registry.integration.test.ts +15 -15
  43. package/src/platform/registry/command-view.ts +10 -12
  44. package/src/platform/registry/index.ts +93 -93
  45. package/src/platform/registry/resource-link.ts +32 -0
  46. package/src/platform/registry/resource-registry.ts +917 -876
  47. package/src/platform/registry/serialization.ts +56 -73
  48. package/src/platform/registry/serialized-types.ts +17 -12
  49. package/src/platform/registry/types.ts +14 -43
  50. package/src/reference/_generated/contracts.md +94 -182
  51. package/src/reference/glossary.md +71 -105
  52. package/src/scaffold-registry/__tests__/index.test.ts +125 -1
  53. package/src/scaffold-registry/__tests__/schema.test.ts +48 -20
  54. package/src/scaffold-registry/index.ts +236 -188
  55. package/src/scaffold-registry/schema.ts +47 -22
  56. package/src/supabase/database.types.ts +2880 -2719
  57. package/src/test-utils/fixtures/memberships.ts +82 -80
  58. package/src/platform/registry/domains.ts +0 -165
@@ -1,894 +1,82 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { buildOrganizationGraph } from '../graph'
3
- import { defineOrganizationModel, resolveOrganizationModel } from '../resolve'
4
-
5
- describe('organization-graph', () => {
6
- it('derives semantic graph nodes and edges from the organization model', () => {
7
- const model = resolveOrganizationModel(
8
- defineOrganizationModel({
9
- branding: {
10
- organizationName: 'Acme',
11
- productName: 'Acme OS',
12
- shortName: 'Acme'
13
- }
14
- })
15
- )
16
-
17
- const graph = buildOrganizationGraph({ organizationModel: model })
18
-
19
- expect(graph.version).toBe(1)
20
- expect(graph.organizationModelVersion).toBe(1)
21
-
22
- const feature = graph.nodes.find((node) => node.id === 'feature:operations')
23
- expect(feature).toMatchObject({
24
- kind: 'feature',
25
- sourceId: 'operations',
26
- label: 'Operations',
27
- enabled: true,
28
- featureId: 'operations'
29
- })
30
-
31
- const disabledFeature = graph.nodes.find((node) => node.id === 'feature:seo')
32
- expect(disabledFeature).toMatchObject({
33
- kind: 'feature',
34
- sourceId: 'seo',
35
- enabled: false
36
- })
37
-
38
- // Features now carry semantic data directly (no separate domain nodes)
39
- const crmFeature = graph.nodes.find((node) => node.id === 'feature:crm')
40
- expect(crmFeature).toMatchObject({
41
- kind: 'feature',
42
- sourceId: 'crm',
43
- label: 'CRM'
44
- })
45
-
46
- expect(graph.nodes.find((node) => node.id === 'surface:crm.pipeline')).toMatchObject({
47
- kind: 'surface',
48
- sourceId: 'crm.pipeline',
49
- surfaceType: 'graph'
50
- })
51
-
52
- expect(graph.nodes.find((node) => node.id === 'entity:crm.deal')).toMatchObject({
53
- kind: 'entity',
54
- sourceId: 'crm.deal',
55
- label: 'crm.deal'
56
- })
57
-
58
- expect(graph.nodes.find((node) => node.id === 'capability:crm.pipeline.manage')).toMatchObject({
59
- kind: 'capability',
60
- sourceId: 'crm.pipeline.manage',
61
- label: 'crm.pipeline.manage'
62
- })
63
-
64
- expect(graph.edges).toEqual(
65
- expect.arrayContaining([
66
- expect.objectContaining({
67
- kind: 'contains',
68
- sourceId: 'organization-model',
69
- targetId: 'feature:crm'
70
- }),
71
- expect.objectContaining({
72
- kind: 'exposes',
73
- sourceId: 'feature:crm',
74
- targetId: 'surface:crm.pipeline'
75
- }),
76
- expect.objectContaining({
77
- kind: 'references',
78
- sourceId: 'surface:crm.pipeline',
79
- targetId: 'feature:crm'
80
- })
81
- ])
82
- )
83
- })
84
-
85
- describe('feature labels', () => {
86
- it('uses the feature label directly from the unified features array', () => {
87
- const model = resolveOrganizationModel(
88
- defineOrganizationModel({
89
- branding: { organizationName: 'Acme', productName: 'Acme OS', shortName: 'Acme' }
90
- })
91
- )
92
-
93
- const graph = buildOrganizationGraph({ organizationModel: model })
94
-
95
- // Verify all default features have their labels from the unified feature definitions
96
- const expectedLabels: Record<string, string> = {
97
- crm: 'CRM',
98
- 'lead-gen': 'Lead Gen',
99
- projects: 'Projects',
100
- operations: 'Operations',
101
- monitoring: 'Monitoring',
102
- settings: 'Settings',
103
- seo: 'SEO'
104
- }
105
-
106
- for (const [featureId, expectedLabel] of Object.entries(expectedLabels)) {
107
- const node = graph.nodes.find((n) => n.id === `feature:${featureId}`)
108
- expect(node, `feature node for '${featureId}' should exist`).toBeDefined()
109
- expect(node?.label).toBe(expectedLabel)
110
- }
111
- })
112
-
113
- it('uses the overridden label when a feature label is customized', () => {
114
- const model = resolveOrganizationModel(
115
- defineOrganizationModel({
116
- branding: { organizationName: 'Acme', productName: 'Acme OS', shortName: 'Acme' },
117
- features: [
118
- {
119
- id: 'crm',
120
- label: 'Deals',
121
- enabled: true,
122
- entityIds: [],
123
- surfaceIds: ['crm.pipeline'],
124
- resourceIds: [],
125
- capabilityIds: []
126
- }
127
- ],
128
- navigation: {
129
- defaultSurfaceId: 'crm.pipeline',
130
- surfaces: [
131
- {
132
- id: 'crm.pipeline',
133
- label: 'Pipeline',
134
- path: '/crm/pipeline',
135
- surfaceType: 'graph',
136
- featureId: 'crm',
137
- featureIds: ['crm'],
138
- entityIds: [],
139
- resourceIds: [],
140
- capabilityIds: []
141
- }
142
- ],
143
- groups: [{ id: 'g', label: 'CRM', placement: 'primary', surfaceIds: ['crm.pipeline'] }]
144
- }
145
- })
146
- )
147
-
148
- const graph = buildOrganizationGraph({ organizationModel: model })
149
-
150
- const node = graph.nodes.find((n) => n.id === 'feature:crm')
151
- expect(node?.label).toBe('Deals')
152
- })
153
- })
154
-
155
- describe('resource node upsert merging', () => {
156
- it('keeps resourceMappings label and merges description from commandViewData', () => {
157
- const model = resolveOrganizationModel(
158
- defineOrganizationModel({
159
- features: [
160
- {
161
- id: 'operations',
162
- label: 'Operations',
163
- enabled: true,
164
- color: 'violet',
165
- entityIds: [],
166
- surfaceIds: ['operations.command-view'],
167
- resourceIds: ['workflow-order'],
168
- capabilityIds: []
169
- }
170
- ],
171
- navigation: {
172
- defaultSurfaceId: 'operations.command-view',
173
- surfaces: [
174
- {
175
- id: 'operations.command-view',
176
- label: 'Command View',
177
- path: '/operations/command-view',
178
- surfaceType: 'graph',
179
- featureId: 'operations',
180
- featureIds: ['operations'],
181
- entityIds: [],
182
- resourceIds: ['workflow-order'],
183
- capabilityIds: []
184
- }
185
- ],
186
- groups: [
187
- {
188
- id: 'primary-operations',
189
- label: 'Operations',
190
- placement: 'primary',
191
- surfaceIds: ['operations.command-view']
192
- }
193
- ]
194
- },
195
- resourceMappings: [
196
- {
197
- id: 'workflow-order-model',
198
- label: 'Order Workflow',
199
- resourceId: 'workflow-order',
200
- resourceType: 'workflow',
201
- featureIds: ['operations'],
202
- entityIds: [],
203
- surfaceIds: ['operations.command-view'],
204
- capabilityIds: []
205
- }
206
- ]
207
- })
208
- )
209
-
210
- const graph = buildOrganizationGraph({
211
- organizationModel: model,
212
- commandViewData: {
213
- workflows: [
214
- {
215
- resourceId: 'workflow-order',
216
- name: 'Order Workflow Runtime',
217
- description: 'Runtime description from commandViewData',
218
- version: '1.0.0',
219
- type: 'workflow',
220
- status: 'prod',
221
- domains: ['operations'],
222
- stepCount: 2,
223
- entryPoint: 'start'
224
- }
225
- ],
226
- agents: [],
227
- triggers: [],
228
- integrations: [],
229
- externalResources: [],
230
- humanCheckpoints: [],
231
- edges: []
232
- }
233
- })
234
-
235
- const resourceNodes = graph.nodes.filter((n) => n.id === 'resource:workflow-order')
236
- expect(resourceNodes).toHaveLength(1)
237
-
238
- const node = resourceNodes[0]
239
- expect(node.label).toBe('Order Workflow')
240
- expect(node.description).toBe('Runtime description from commandViewData')
241
- })
242
-
243
- it('preserves existing non-default label even when commandViewData provides a different name', () => {
244
- const model = resolveOrganizationModel(
245
- defineOrganizationModel({
246
- features: [
247
- {
248
- id: 'ops',
249
- label: 'Ops',
250
- enabled: true,
251
- color: 'blue',
252
- entityIds: [],
253
- surfaceIds: ['ops.view'],
254
- resourceIds: ['res-a'],
255
- capabilityIds: []
256
- }
257
- ],
258
- navigation: {
259
- defaultSurfaceId: 'ops.view',
260
- surfaces: [
261
- {
262
- id: 'ops.view',
263
- label: 'Ops View',
264
- path: '/ops/view',
265
- surfaceType: 'graph',
266
- featureId: 'operations',
267
- featureIds: ['ops'],
268
- entityIds: [],
269
- resourceIds: ['res-a'],
270
- capabilityIds: []
271
- }
272
- ],
273
- groups: [{ id: 'g', label: 'Ops', placement: 'primary', surfaceIds: ['ops.view'] }]
274
- },
275
- resourceMappings: [
276
- {
277
- id: 'res-a-model',
278
- label: 'My Custom Label',
279
- resourceId: 'res-a',
280
- resourceType: 'workflow',
281
- featureIds: ['ops'],
282
- entityIds: [],
283
- surfaceIds: ['ops.view'],
284
- capabilityIds: []
285
- }
286
- ]
287
- })
288
- )
289
-
290
- const graph = buildOrganizationGraph({
291
- organizationModel: model,
292
- commandViewData: {
293
- workflows: [
294
- {
295
- resourceId: 'res-a',
296
- name: 'Runtime Name Should Not Override',
297
- description: 'desc',
298
- version: '1.0.0',
299
- type: 'workflow',
300
- status: 'prod',
301
- domains: ['ops'],
302
- stepCount: 1,
303
- entryPoint: 'start'
304
- }
305
- ],
306
- agents: [],
307
- triggers: [],
308
- integrations: [],
309
- externalResources: [],
310
- humanCheckpoints: [],
311
- edges: []
312
- }
313
- })
314
-
315
- const node = graph.nodes.find((n) => n.id === 'resource:res-a')
316
- expect(node?.label).toBe('My Custom Label')
317
- })
318
- })
319
-
320
- describe('de-duplication of graph elements', () => {
321
- it('creates only one feature node when multiple surfaces reference the same feature', () => {
322
- const model = resolveOrganizationModel(
323
- defineOrganizationModel({
324
- features: [
325
- {
326
- id: 'crm',
327
- label: 'CRM',
328
- enabled: true,
329
- color: 'blue',
330
- entityIds: [],
331
- surfaceIds: ['crm.pipeline', 'crm.accounts'],
332
- resourceIds: [],
333
- capabilityIds: []
334
- }
335
- ],
336
- navigation: {
337
- defaultSurfaceId: 'crm.pipeline',
338
- surfaces: [
339
- {
340
- id: 'crm.pipeline',
341
- label: 'Pipeline',
342
- path: '/crm/pipeline',
343
- surfaceType: 'graph',
344
- featureId: 'crm',
345
- featureIds: ['crm'],
346
- entityIds: [],
347
- resourceIds: [],
348
- capabilityIds: []
349
- },
350
- {
351
- id: 'crm.accounts',
352
- label: 'Accounts',
353
- path: '/crm/accounts',
354
- surfaceType: 'list',
355
- featureId: 'crm',
356
- featureIds: ['crm'],
357
- entityIds: [],
358
- resourceIds: [],
359
- capabilityIds: []
360
- }
361
- ],
362
- groups: [{ id: 'g', label: 'CRM', placement: 'primary', surfaceIds: ['crm.pipeline', 'crm.accounts'] }]
363
- }
364
- })
365
- )
366
-
367
- const graph = buildOrganizationGraph({ organizationModel: model })
368
-
369
- const featureNodes = graph.nodes.filter((n) => n.id === 'feature:crm')
370
- expect(featureNodes).toHaveLength(1)
371
- })
372
-
373
- it('creates only one entity node when the same entity appears in both a feature and a surface', () => {
374
- const model = resolveOrganizationModel(
375
- defineOrganizationModel({
376
- features: [
377
- {
378
- id: 'crm',
379
- label: 'CRM',
380
- enabled: true,
381
- color: 'blue',
382
- entityIds: ['crm.deal'],
383
- surfaceIds: ['crm.pipeline'],
384
- resourceIds: [],
385
- capabilityIds: []
386
- }
387
- ],
388
- navigation: {
389
- defaultSurfaceId: 'crm.pipeline',
390
- surfaces: [
391
- {
392
- id: 'crm.pipeline',
393
- label: 'Pipeline',
394
- path: '/crm/pipeline',
395
- surfaceType: 'graph',
396
- featureId: 'crm',
397
- featureIds: ['crm'],
398
- entityIds: ['crm.deal'],
399
- resourceIds: [],
400
- capabilityIds: []
401
- }
402
- ],
403
- groups: [{ id: 'g', label: 'CRM', placement: 'primary', surfaceIds: ['crm.pipeline'] }]
404
- }
405
- })
406
- )
407
-
408
- const graph = buildOrganizationGraph({ organizationModel: model })
409
-
410
- const entityNodes = graph.nodes.filter((n) => n.id === 'entity:crm.deal')
411
- expect(entityNodes).toHaveLength(1)
412
- })
413
-
414
- it('creates only one resource node when the same resource appears in resourceMappings and commandViewData', () => {
415
- const model = resolveOrganizationModel(
416
- defineOrganizationModel({
417
- features: [
418
- {
419
- id: 'ops',
420
- label: 'Ops',
421
- enabled: true,
422
- color: 'violet',
423
- entityIds: [],
424
- surfaceIds: ['ops.view'],
425
- resourceIds: ['wf-shared'],
426
- capabilityIds: []
427
- }
428
- ],
429
- navigation: {
430
- defaultSurfaceId: 'ops.view',
431
- surfaces: [
432
- {
433
- id: 'ops.view',
434
- label: 'Ops View',
435
- path: '/ops/view',
436
- surfaceType: 'graph',
437
- featureId: 'operations',
438
- featureIds: ['ops'],
439
- entityIds: [],
440
- resourceIds: ['wf-shared'],
441
- capabilityIds: []
442
- }
443
- ],
444
- groups: [{ id: 'g', label: 'Ops', placement: 'primary', surfaceIds: ['ops.view'] }]
445
- },
446
- resourceMappings: [
447
- {
448
- id: 'wf-shared-model',
449
- label: 'Shared Workflow',
450
- resourceId: 'wf-shared',
451
- resourceType: 'workflow',
452
- featureIds: ['ops'],
453
- entityIds: [],
454
- surfaceIds: ['ops.view'],
455
- capabilityIds: []
456
- }
457
- ]
458
- })
459
- )
460
-
461
- const graph = buildOrganizationGraph({
462
- organizationModel: model,
463
- commandViewData: {
464
- workflows: [
465
- {
466
- resourceId: 'wf-shared',
467
- name: 'Shared Workflow Runtime',
468
- description: 'desc',
469
- version: '1.0.0',
470
- type: 'workflow',
471
- status: 'prod',
472
- domains: ['ops'],
473
- stepCount: 1,
474
- entryPoint: 'start'
475
- }
476
- ],
477
- agents: [],
478
- triggers: [],
479
- integrations: [],
480
- externalResources: [],
481
- humanCheckpoints: [],
482
- edges: []
483
- }
484
- })
485
-
486
- const resourceNodes = graph.nodes.filter((n) => n.id === 'resource:wf-shared')
487
- expect(resourceNodes).toHaveLength(1)
488
- })
489
- })
490
-
491
- describe('commandViewData edge relationships', () => {
492
- it('creates a stub resource node with label === resourceId for edges referencing unknown resources', () => {
493
- const model = resolveOrganizationModel(
494
- defineOrganizationModel({
495
- features: [
496
- {
497
- id: 'ops',
498
- label: 'Ops',
499
- enabled: true,
500
- color: 'violet',
501
- entityIds: [],
502
- surfaceIds: [],
503
- resourceIds: [],
504
- capabilityIds: []
505
- }
506
- ],
507
- navigation: {
508
- defaultSurfaceId: 'ops.view',
509
- surfaces: [
510
- {
511
- id: 'ops.view',
512
- label: 'Ops View',
513
- path: '/ops/view',
514
- surfaceType: 'graph',
515
- featureId: 'operations',
516
- featureIds: [],
517
- entityIds: [],
518
- resourceIds: [],
519
- capabilityIds: []
520
- }
521
- ],
522
- groups: [{ id: 'g', label: 'Ops', placement: 'primary', surfaceIds: ['ops.view'] }]
523
- }
524
- })
525
- )
526
-
527
- const graph = buildOrganizationGraph({
528
- organizationModel: model,
529
- commandViewData: {
530
- workflows: [],
531
- agents: [],
532
- triggers: [],
533
- integrations: [],
534
- externalResources: [],
535
- humanCheckpoints: [],
536
- edges: [
537
- {
538
- id: 'edge-unknown',
539
- source: 'unknown-source',
540
- target: 'unknown-target',
541
- relationship: 'triggers'
542
- }
543
- ]
544
- }
545
- })
546
-
547
- const sourceNode = graph.nodes.find((n) => n.id === 'resource:unknown-source')
548
- expect(sourceNode).toBeDefined()
549
- expect(sourceNode?.label).toBe('unknown-source')
550
- expect(sourceNode?.sourceId).toBe('unknown-source')
551
-
552
- const targetNode = graph.nodes.find((n) => n.id === 'resource:unknown-target')
553
- expect(targetNode).toBeDefined()
554
- expect(targetNode?.label).toBe('unknown-target')
555
- expect(targetNode?.sourceId).toBe('unknown-target')
556
- })
557
-
558
- it('normalizes humanCheckpoints type to human_checkpoint on resource nodes', () => {
559
- const model = resolveOrganizationModel(
560
- defineOrganizationModel({
561
- features: [
562
- {
563
- id: 'ops',
564
- label: 'Ops',
565
- enabled: true,
566
- color: 'violet',
567
- entityIds: [],
568
- surfaceIds: [],
569
- resourceIds: [],
570
- capabilityIds: []
571
- }
572
- ],
573
- navigation: {
574
- defaultSurfaceId: 'ops.view',
575
- surfaces: [
576
- {
577
- id: 'ops.view',
578
- label: 'Ops View',
579
- path: '/ops/view',
580
- surfaceType: 'graph',
581
- featureId: 'operations',
582
- featureIds: [],
583
- entityIds: [],
584
- resourceIds: [],
585
- capabilityIds: []
586
- }
587
- ],
588
- groups: [{ id: 'g', label: 'Ops', placement: 'primary', surfaceIds: ['ops.view'] }]
589
- }
590
- })
591
- )
592
-
593
- const graph = buildOrganizationGraph({
594
- organizationModel: model,
595
- commandViewData: {
596
- workflows: [],
597
- agents: [],
598
- triggers: [],
599
- integrations: [],
600
- externalResources: [],
601
- humanCheckpoints: [
602
- {
603
- resourceId: 'approval-step',
604
- name: 'Approval Step',
605
- description: 'Human review gate',
606
- version: '1.0.0',
607
- type: 'human',
608
- status: 'prod',
609
- domains: ['ops']
610
- }
611
- ],
612
- edges: []
613
- }
614
- })
615
-
616
- const node = graph.nodes.find((n) => n.id === 'resource:approval-step')
617
- expect(node).toBeDefined()
618
- expect(node?.resourceType).toBe('human_checkpoint')
619
- })
620
-
621
- it('creates a references edge with label and relationshipType for commandViewData edges', () => {
622
- const model = resolveOrganizationModel(
623
- defineOrganizationModel({
624
- features: [
625
- {
626
- id: 'ops',
627
- label: 'Ops',
628
- enabled: true,
629
- color: 'violet',
630
- entityIds: [],
631
- surfaceIds: [],
632
- resourceIds: [],
633
- capabilityIds: []
634
- }
635
- ],
636
- navigation: {
637
- defaultSurfaceId: 'ops.view',
638
- surfaces: [
639
- {
640
- id: 'ops.view',
641
- label: 'Ops View',
642
- path: '/ops/view',
643
- surfaceType: 'graph',
644
- featureId: 'operations',
645
- featureIds: [],
646
- entityIds: [],
647
- resourceIds: [],
648
- capabilityIds: []
649
- }
650
- ],
651
- groups: [{ id: 'g', label: 'Ops', placement: 'primary', surfaceIds: ['ops.view'] }]
652
- }
653
- })
654
- )
655
-
656
- const graph = buildOrganizationGraph({
657
- organizationModel: model,
658
- commandViewData: {
659
- workflows: [],
660
- agents: [],
661
- triggers: [],
662
- integrations: [],
663
- externalResources: [],
664
- humanCheckpoints: [],
665
- edges: [
666
- {
667
- id: 'edge-triggers',
668
- source: 'svc-a',
669
- target: 'svc-b',
670
- relationship: 'triggers'
671
- }
672
- ]
673
- }
674
- })
675
-
676
- expect(graph.edges).toEqual(
677
- expect.arrayContaining([
678
- expect.objectContaining({
679
- kind: 'references',
680
- sourceId: 'resource:svc-a',
681
- targetId: 'resource:svc-b',
682
- label: 'triggers',
683
- relationshipType: 'triggers'
684
- })
685
- ])
686
- )
687
- })
688
- })
689
-
690
- it('bridges command view topology into resource nodes and labeled relationship edges', () => {
691
- const model = resolveOrganizationModel(
692
- defineOrganizationModel({
693
- features: [
694
- {
695
- id: 'operations',
696
- label: 'Operations',
697
- description: 'Operational resources, topology, and orchestration visibility',
698
- enabled: true,
699
- color: 'violet',
700
- entityIds: [],
701
- surfaceIds: ['operations.command-view'],
702
- resourceIds: ['workflow-order'],
703
- capabilityIds: ['operations.command-view']
704
- }
705
- ],
706
- navigation: {
707
- defaultSurfaceId: 'operations.command-view',
708
- surfaces: [
709
- {
710
- id: 'operations.command-view',
711
- label: 'Command View',
712
- path: '/operations/command-view',
713
- surfaceType: 'graph',
714
- featureId: 'operations',
715
- featureIds: ['operations'],
716
- entityIds: [],
717
- resourceIds: ['workflow-order'],
718
- capabilityIds: ['operations.command-view']
719
- }
720
- ],
721
- groups: [
722
- {
723
- id: 'primary-operations',
724
- label: 'Operations',
725
- placement: 'primary',
726
- surfaceIds: ['operations.command-view']
727
- }
728
- ]
729
- },
730
- resourceMappings: [
731
- {
732
- id: 'workflow-order-model',
733
- label: 'Order Workflow Model',
734
- resourceId: 'workflow-order',
735
- resourceType: 'workflow',
736
- featureIds: ['operations'],
737
- entityIds: [],
738
- surfaceIds: ['operations.command-view'],
739
- capabilityIds: []
740
- }
741
- ]
742
- })
743
- )
744
-
745
- const graph = buildOrganizationGraph({
746
- organizationModel: model,
747
- commandViewData: {
748
- workflows: [
749
- {
750
- resourceId: 'workflow-order',
751
- name: 'Order Workflow Runtime',
752
- description: 'Runtime workflow surfaced by Command View',
753
- version: '1.0.0',
754
- type: 'workflow',
755
- status: 'prod',
756
- domains: ['operations'],
757
- stepCount: 3,
758
- entryPoint: 'start'
759
- }
760
- ],
761
- agents: [
762
- {
763
- resourceId: 'agent-order',
764
- name: 'Order Agent Runtime',
765
- description: 'Runtime agent surfaced by Command View',
766
- version: '1.0.0',
767
- type: 'agent',
768
- status: 'prod',
769
- domains: ['operations'],
770
- modelProvider: 'openai',
771
- modelId: 'gpt-4.1',
772
- toolCount: 2,
773
- hasKnowledgeMap: true,
774
- hasMemory: false,
775
- sessionCapable: true
776
- }
777
- ],
778
- triggers: [],
779
- integrations: [
780
- {
781
- resourceId: 'integration-stripe',
782
- name: 'Stripe Runtime',
783
- description: 'Runtime integration surfaced by Command View',
784
- version: '1.0.0',
785
- type: 'integration',
786
- status: 'prod',
787
- domains: ['operations'],
788
- provider: 'stripe',
789
- credentialName: 'stripe-prod'
790
- }
791
- ],
792
- externalResources: [],
793
- humanCheckpoints: [
794
- {
795
- resourceId: 'approval-high-value',
796
- name: 'High Value Approval',
797
- description: 'Runtime human checkpoint surfaced by Command View',
798
- version: '1.0.0',
799
- type: 'human',
800
- status: 'prod',
801
- domains: ['operations']
802
- }
803
- ],
804
- edges: [
805
- {
806
- id: 'edge-runtime-triggers',
807
- source: 'agent-order',
808
- target: 'workflow-order',
809
- relationship: 'triggers'
810
- },
811
- {
812
- id: 'edge-runtime-uses',
813
- source: 'workflow-order',
814
- target: 'integration-stripe',
815
- relationship: 'uses'
816
- },
817
- {
818
- id: 'edge-runtime-approval',
819
- source: 'workflow-order',
820
- target: 'approval-high-value',
821
- relationship: 'approval'
822
- }
823
- ]
824
- }
825
- })
826
-
827
- expect(graph.nodes.find((node) => node.id === 'resource:workflow-order')).toMatchObject({
828
- kind: 'resource',
829
- sourceId: 'workflow-order',
830
- resourceType: 'workflow',
831
- label: 'Order Workflow Model'
832
- })
833
-
834
- expect(graph.nodes.find((node) => node.id === 'resource:agent-order')).toMatchObject({
835
- kind: 'resource',
836
- sourceId: 'agent-order',
837
- resourceType: 'agent',
838
- label: 'Order Agent Runtime'
839
- })
840
-
841
- expect(graph.nodes.find((node) => node.id === 'resource:approval-high-value')).toMatchObject({
842
- kind: 'resource',
843
- sourceId: 'approval-high-value',
844
- resourceType: 'human_checkpoint',
845
- label: 'High Value Approval'
846
- })
847
-
848
- expect(graph.edges).toEqual(
849
- expect.arrayContaining([
850
- expect.objectContaining({
851
- kind: 'contains',
852
- sourceId: 'organization-model',
853
- targetId: 'resource:agent-order'
854
- }),
855
- expect.objectContaining({
856
- kind: 'references',
857
- sourceId: 'resource:agent-order',
858
- targetId: 'feature:operations'
859
- }),
860
- expect.objectContaining({
861
- kind: 'references',
862
- sourceId: 'resource:agent-order',
863
- targetId: 'resource:workflow-order',
864
- label: 'triggers',
865
- relationshipType: 'triggers'
866
- }),
867
- expect.objectContaining({
868
- kind: 'references',
869
- sourceId: 'resource:workflow-order',
870
- targetId: 'resource:integration-stripe',
871
- label: 'uses',
872
- relationshipType: 'uses'
873
- }),
874
- expect.objectContaining({
875
- kind: 'references',
876
- sourceId: 'resource:workflow-order',
877
- targetId: 'resource:approval-high-value',
878
- label: 'approval',
879
- relationshipType: 'approval'
880
- }),
881
- expect.objectContaining({
882
- kind: 'maps_to',
883
- sourceId: 'resource:workflow-order',
884
- targetId: 'feature:operations'
885
- }),
886
- expect.objectContaining({
887
- kind: 'maps_to',
888
- sourceId: 'resource:workflow-order',
889
- targetId: 'surface:operations.command-view'
890
- })
891
- ])
892
- )
893
- })
894
- })
1
+ import { describe, expect, it } from 'vitest'
2
+ import { buildOrganizationGraph } from '../graph/build'
3
+ import { resolveOrganizationModel } from '../resolve'
4
+
5
+ describe('organization graph', () => {
6
+ it('emits feature nodes from the flat features array', () => {
7
+ const graph = buildOrganizationGraph({ organizationModel: resolveOrganizationModel() })
8
+
9
+ expect(graph.nodes.find((node) => node.id === 'feature:sales.crm')).toMatchObject({
10
+ kind: 'feature',
11
+ sourceId: 'sales.crm',
12
+ label: 'CRM'
13
+ })
14
+ expect(graph.edges.find((edge) => edge.sourceId === 'feature:sales' && edge.targetId === 'feature:sales.crm')).toMatchObject({
15
+ kind: 'contains'
16
+ })
17
+ })
18
+
19
+ it('uses overridden feature labels', () => {
20
+ const model = resolveOrganizationModel({
21
+ features: [{ id: 'custom', label: 'Custom Workspace', enabled: true, path: '/custom' }]
22
+ })
23
+ const graph = buildOrganizationGraph({ organizationModel: model })
24
+
25
+ expect(graph.nodes.find((node) => node.id === 'feature:custom')?.label).toBe('Custom Workspace')
26
+ })
27
+
28
+ it('bridges command view resources and relationships', () => {
29
+ const model = resolveOrganizationModel({
30
+ features: [{ id: 'operations', label: 'Operations', enabled: true, path: '/operations' }]
31
+ })
32
+ const graph = buildOrganizationGraph({
33
+ organizationModel: model,
34
+ commandViewData: {
35
+ generatedAt: '2026-04-24T00:00:00.000Z',
36
+ workflows: [
37
+ {
38
+ resourceId: 'workflow-order',
39
+ name: 'Order Workflow',
40
+ description: 'Order workflow',
41
+ type: 'workflow',
42
+ links: [],
43
+ status: 'active'
44
+ }
45
+ ],
46
+ agents: [],
47
+ triggers: [],
48
+ integrations: [],
49
+ externalResources: [],
50
+ humanCheckpoints: [],
51
+ edges: [
52
+ {
53
+ id: 'edge-1',
54
+ source: 'workflow-order',
55
+ target: 'missing-resource',
56
+ relationship: 'uses'
57
+ }
58
+ ],
59
+ stats: {
60
+ totalResources: 1,
61
+ activeResources: 1,
62
+ inactiveResources: 0,
63
+ resourcesByType: {}
64
+ }
65
+ }
66
+ })
67
+
68
+ expect(graph.nodes.find((node) => node.id === 'resource:workflow-order')).toMatchObject({
69
+ kind: 'resource',
70
+ sourceId: 'workflow-order',
71
+ label: 'Order Workflow'
72
+ })
73
+ expect(graph.nodes.find((node) => node.id === 'resource:missing-resource')).toMatchObject({
74
+ kind: 'resource',
75
+ label: 'missing-resource'
76
+ })
77
+ expect(graph.edges.find((edge) => edge.id.includes('uses'))).toMatchObject({
78
+ kind: 'references',
79
+ relationshipType: 'uses'
80
+ })
81
+ })
82
+ })