@elevasis/core 0.24.0 → 0.24.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.d.ts +3192 -2313
- package/dist/index.js +243 -13
- package/dist/knowledge/index.d.ts +92 -6
- package/dist/organization-model/index.d.ts +3192 -2313
- package/dist/organization-model/index.js +243 -13
- package/dist/test-utils/index.d.ts +134 -45
- package/dist/test-utils/index.js +118 -11
- package/package.json +3 -3
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +45 -7
- package/src/execution/engine/workflow/types.ts +5 -7
- package/src/organization-model/__tests__/domains/resources.test.ts +19 -8
- package/src/organization-model/__tests__/domains/topology.test.ts +188 -0
- package/src/organization-model/__tests__/graph.test.ts +98 -7
- package/src/organization-model/__tests__/schema.test.ts +14 -4
- package/src/organization-model/defaults.ts +2 -0
- package/src/organization-model/domains/resources.ts +63 -20
- package/src/organization-model/domains/topology.ts +261 -0
- package/src/organization-model/graph/build.ts +63 -15
- package/src/organization-model/graph/schema.ts +4 -3
- package/src/organization-model/graph/types.ts +5 -4
- package/src/organization-model/index.ts +4 -3
- package/src/organization-model/ontology.ts +2 -5
- package/src/organization-model/organization-model.mdx +16 -11
- package/src/organization-model/published.ts +36 -13
- package/src/organization-model/schema.ts +51 -11
- package/src/organization-model/types.ts +25 -11
- package/src/platform/registry/__tests__/validation.test.ts +199 -14
- package/src/platform/registry/resource-registry.ts +11 -11
- package/src/platform/registry/validation.ts +226 -34
- package/src/reference/_generated/contracts.md +45 -7
- package/src/reference/glossary.md +3 -3
- package/src/supabase/database.types.ts +3156 -3153
|
@@ -61,10 +61,18 @@ import {
|
|
|
61
61
|
ResourceOntologyBindingSchema,
|
|
62
62
|
ResourcesDomainSchema,
|
|
63
63
|
ScriptResourceEntrySchema,
|
|
64
|
-
ScriptResourceLanguageSchema,
|
|
65
|
-
ScriptResourceSourceSchema,
|
|
66
|
-
WorkflowResourceEntrySchema
|
|
67
|
-
} from './domains/resources'
|
|
64
|
+
ScriptResourceLanguageSchema,
|
|
65
|
+
ScriptResourceSourceSchema,
|
|
66
|
+
WorkflowResourceEntrySchema
|
|
67
|
+
} from './domains/resources'
|
|
68
|
+
import {
|
|
69
|
+
OmTopologyDomainSchema,
|
|
70
|
+
OmTopologyMetadataSchema,
|
|
71
|
+
OmTopologyNodeKindSchema,
|
|
72
|
+
OmTopologyNodeRefSchema,
|
|
73
|
+
OmTopologyRelationshipKindSchema,
|
|
74
|
+
OmTopologyRelationshipSchema
|
|
75
|
+
} from './domains/topology'
|
|
68
76
|
import {
|
|
69
77
|
ActionsDomainSchema,
|
|
70
78
|
ActionIdSchema,
|
|
@@ -159,13 +167,19 @@ export type OrganizationModelResourceKind = z.infer<typeof ResourceKindSchema>
|
|
|
159
167
|
export type OrganizationModelResourceGovernanceStatus = z.infer<typeof ResourceGovernanceStatusSchema>
|
|
160
168
|
export type OrganizationModelResourceOntologyBinding = z.infer<typeof ResourceOntologyBindingSchema>
|
|
161
169
|
export type OrganizationModelAgentKind = z.infer<typeof AgentKindSchema>
|
|
162
|
-
export type OrganizationModelScriptResourceLanguage = z.infer<typeof ScriptResourceLanguageSchema>
|
|
163
|
-
export type OrganizationModelScriptResourceSource = z.infer<typeof ScriptResourceSourceSchema>
|
|
164
|
-
export type OrganizationModelWorkflowResourceEntry = z.infer<typeof WorkflowResourceEntrySchema>
|
|
165
|
-
export type OrganizationModelAgentResourceEntry = z.infer<typeof AgentResourceEntrySchema>
|
|
166
|
-
export type OrganizationModelIntegrationResourceEntry = z.infer<typeof IntegrationResourceEntrySchema>
|
|
167
|
-
export type OrganizationModelScriptResourceEntry = z.infer<typeof ScriptResourceEntrySchema>
|
|
168
|
-
export type
|
|
170
|
+
export type OrganizationModelScriptResourceLanguage = z.infer<typeof ScriptResourceLanguageSchema>
|
|
171
|
+
export type OrganizationModelScriptResourceSource = z.infer<typeof ScriptResourceSourceSchema>
|
|
172
|
+
export type OrganizationModelWorkflowResourceEntry = z.infer<typeof WorkflowResourceEntrySchema>
|
|
173
|
+
export type OrganizationModelAgentResourceEntry = z.infer<typeof AgentResourceEntrySchema>
|
|
174
|
+
export type OrganizationModelIntegrationResourceEntry = z.infer<typeof IntegrationResourceEntrySchema>
|
|
175
|
+
export type OrganizationModelScriptResourceEntry = z.infer<typeof ScriptResourceEntrySchema>
|
|
176
|
+
export type OrganizationModelTopology = z.infer<typeof OmTopologyDomainSchema>
|
|
177
|
+
export type OrganizationModelTopologyNodeKind = z.infer<typeof OmTopologyNodeKindSchema>
|
|
178
|
+
export type OrganizationModelTopologyNodeRef = z.infer<typeof OmTopologyNodeRefSchema>
|
|
179
|
+
export type OrganizationModelTopologyRelationshipKind = z.infer<typeof OmTopologyRelationshipKindSchema>
|
|
180
|
+
export type OrganizationModelTopologyRelationship = z.infer<typeof OmTopologyRelationshipSchema>
|
|
181
|
+
export type OrganizationModelTopologyMetadata = z.infer<typeof OmTopologyMetadataSchema>
|
|
182
|
+
export type OrganizationModelActions = z.infer<typeof ActionsDomainSchema>
|
|
169
183
|
export type OrganizationModelAction = z.infer<typeof ActionSchema>
|
|
170
184
|
export type OrganizationModelActionId = z.infer<typeof ActionIdSchema>
|
|
171
185
|
export type OrganizationModelActionScope = z.infer<typeof ActionScopeSchema>
|
|
@@ -304,13 +304,24 @@ describe('validateResourceGovernance', () => {
|
|
|
304
304
|
order: 20
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
const workflowResource: ResourceEntry = {
|
|
308
|
-
id: 'lead-import',
|
|
309
|
-
kind: 'workflow',
|
|
310
|
-
systemPath: 'sys.lead-gen',
|
|
311
|
-
status: 'active',
|
|
312
|
-
order: 10
|
|
313
|
-
}
|
|
307
|
+
const workflowResource: ResourceEntry = {
|
|
308
|
+
id: 'lead-import',
|
|
309
|
+
kind: 'workflow',
|
|
310
|
+
systemPath: 'sys.lead-gen',
|
|
311
|
+
status: 'active',
|
|
312
|
+
order: 10
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const actionBackedWorkflowResource: ResourceEntry = {
|
|
316
|
+
...workflowResource,
|
|
317
|
+
ontology: {
|
|
318
|
+
actions: ['sys.lead-gen:action/import-leads'],
|
|
319
|
+
primaryAction: 'sys.lead-gen:action/import-leads',
|
|
320
|
+
reads: ['sys.lead-gen:object/company'],
|
|
321
|
+
usesCatalogs: ['sys.lead-gen:catalog/source'],
|
|
322
|
+
emits: ['sys.lead-gen:event/imported']
|
|
323
|
+
}
|
|
324
|
+
}
|
|
314
325
|
|
|
315
326
|
const agentResource: ResourceEntry = {
|
|
316
327
|
id: 'lead-import',
|
|
@@ -361,10 +372,42 @@ describe('validateResourceGovernance', () => {
|
|
|
361
372
|
entryPoint: 'start'
|
|
362
373
|
})
|
|
363
374
|
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
375
|
+
const validOntology = {
|
|
376
|
+
objectTypes: {
|
|
377
|
+
'sys.lead-gen:object/company': {
|
|
378
|
+
id: 'sys.lead-gen:object/company',
|
|
379
|
+
label: 'Company'
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
actionTypes: {
|
|
383
|
+
'sys.lead-gen:action/import-leads': {
|
|
384
|
+
id: 'sys.lead-gen:action/import-leads',
|
|
385
|
+
label: 'Import leads'
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
catalogTypes: {
|
|
389
|
+
'sys.lead-gen:catalog/source': {
|
|
390
|
+
id: 'sys.lead-gen:catalog/source',
|
|
391
|
+
label: 'Source'
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
eventTypes: {
|
|
395
|
+
'sys.lead-gen:event/imported': {
|
|
396
|
+
id: 'sys.lead-gen:event/imported',
|
|
397
|
+
label: 'Imported'
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const createModel = (
|
|
403
|
+
resources: ResourceEntry[] = [workflowResource],
|
|
404
|
+
systems = [systemA],
|
|
405
|
+
extras: Record<string, unknown> = {}
|
|
406
|
+
) => ({
|
|
407
|
+
systems: Object.fromEntries(systems.map((s) => [s.id, s])),
|
|
408
|
+
resources: Object.fromEntries(resources.map((r) => [r.id, r])),
|
|
409
|
+
...extras
|
|
410
|
+
})
|
|
368
411
|
|
|
369
412
|
it('passes when code resources are descriptor-backed and match active OM Resources and Systems', () => {
|
|
370
413
|
const result = validateResourceGovernance(
|
|
@@ -484,7 +527,7 @@ describe('validateResourceGovernance', () => {
|
|
|
484
527
|
).toThrow("Resource 'lead-import' type mismatch: code has 'workflow', OM has 'agent'")
|
|
485
528
|
})
|
|
486
529
|
|
|
487
|
-
it('reports descriptor/OM system mismatches', () => {
|
|
530
|
+
it('reports descriptor/OM system mismatches', () => {
|
|
488
531
|
const codeDescriptor: ResourceEntry = {
|
|
489
532
|
...workflowResource,
|
|
490
533
|
systemPath: 'sys.crm'
|
|
@@ -500,8 +543,150 @@ describe('validateResourceGovernance', () => {
|
|
|
500
543
|
createModel([workflowResource], [systemA, systemB]),
|
|
501
544
|
{ mode: 'strict' }
|
|
502
545
|
)
|
|
503
|
-
).toThrow("Resource 'lead-import' system mismatch: code descriptor has 'sys.crm', OM has 'sys.lead-gen'")
|
|
504
|
-
})
|
|
546
|
+
).toThrow("Resource 'lead-import' system mismatch: code descriptor has 'sys.crm', OM has 'sys.lead-gen'")
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
it('reports descriptor/OM ontology mismatches', () => {
|
|
550
|
+
const codeDescriptor: ResourceEntry = {
|
|
551
|
+
...workflowResource,
|
|
552
|
+
ontology: undefined
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
expect(() =>
|
|
556
|
+
validateResourceGovernance(
|
|
557
|
+
'test-org',
|
|
558
|
+
{
|
|
559
|
+
version: '1.0.0',
|
|
560
|
+
workflows: [createGovernedWorkflow(codeDescriptor)]
|
|
561
|
+
},
|
|
562
|
+
createModel([actionBackedWorkflowResource], [systemA], { ontology: validOntology }),
|
|
563
|
+
{ mode: 'strict' }
|
|
564
|
+
)
|
|
565
|
+
).toThrow("Resource 'lead-import' ontology descriptor mismatch between code and OM")
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
it('reports missing ontology action refs against compiled OM records', () => {
|
|
569
|
+
const resourceWithMissingAction: ResourceEntry = {
|
|
570
|
+
...workflowResource,
|
|
571
|
+
ontology: {
|
|
572
|
+
actions: ['sys.lead-gen:action/missing'],
|
|
573
|
+
primaryAction: 'sys.lead-gen:action/missing'
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
expect(() =>
|
|
578
|
+
validateResourceGovernance(
|
|
579
|
+
'test-org',
|
|
580
|
+
{
|
|
581
|
+
version: '1.0.0',
|
|
582
|
+
workflows: [createGovernedWorkflow(resourceWithMissingAction)]
|
|
583
|
+
},
|
|
584
|
+
createModel([resourceWithMissingAction], [systemA], { ontology: validOntology }),
|
|
585
|
+
{ mode: 'strict' }
|
|
586
|
+
)
|
|
587
|
+
).toThrow(
|
|
588
|
+
"Resource 'lead-import' ontology.actions references missing action ontology record 'sys.lead-gen:action/missing'"
|
|
589
|
+
)
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
it('reports new ontology diagnostics without throwing in warn-only mode', () => {
|
|
593
|
+
const warnings: string[] = []
|
|
594
|
+
const resourceWithMissingAction: ResourceEntry = {
|
|
595
|
+
...workflowResource,
|
|
596
|
+
ontology: {
|
|
597
|
+
actions: ['sys.lead-gen:action/missing'],
|
|
598
|
+
primaryAction: 'sys.lead-gen:action/missing'
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const result = validateResourceGovernance(
|
|
603
|
+
'test-org',
|
|
604
|
+
{
|
|
605
|
+
version: '1.0.0',
|
|
606
|
+
workflows: [createGovernedWorkflow(resourceWithMissingAction)]
|
|
607
|
+
},
|
|
608
|
+
createModel([resourceWithMissingAction], [systemA], { ontology: validOntology }),
|
|
609
|
+
{
|
|
610
|
+
mode: 'warn-only',
|
|
611
|
+
onWarning: (issue) => warnings.push(issue.message)
|
|
612
|
+
}
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
expect(result.valid).toBe(false)
|
|
616
|
+
expect(result.issues.map((issue) => issue.type)).toContain('ontology-reference-missing')
|
|
617
|
+
expect(warnings).toContain(
|
|
618
|
+
"[test-org] Resource 'lead-import' ontology.actions references missing action ontology record 'sys.lead-gen:action/missing'."
|
|
619
|
+
)
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
it('reports primaryAction values outside ontology.actions', () => {
|
|
623
|
+
const resourceWithMismatchedPrimaryAction: ResourceEntry = {
|
|
624
|
+
...workflowResource,
|
|
625
|
+
ontology: {
|
|
626
|
+
actions: ['sys.lead-gen:action/import-leads'],
|
|
627
|
+
primaryAction: 'sys.lead-gen:action/other'
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
expect(() =>
|
|
632
|
+
validateResourceGovernance(
|
|
633
|
+
'test-org',
|
|
634
|
+
{
|
|
635
|
+
version: '1.0.0',
|
|
636
|
+
workflows: [createGovernedWorkflow(resourceWithMismatchedPrimaryAction)]
|
|
637
|
+
},
|
|
638
|
+
createModel([resourceWithMismatchedPrimaryAction], [systemA], { ontology: validOntology }),
|
|
639
|
+
{ mode: 'strict' }
|
|
640
|
+
)
|
|
641
|
+
).toThrow("Resource 'lead-import' primaryAction 'sys.lead-gen:action/other' must be included in ontology.actions")
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
it('reports action-backed workflow descriptors that omit ontology.actions', () => {
|
|
645
|
+
const resourceWithoutActions: ResourceEntry = {
|
|
646
|
+
...workflowResource,
|
|
647
|
+
ontology: {
|
|
648
|
+
reads: ['sys.lead-gen:object/company']
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
expect(() =>
|
|
653
|
+
validateResourceGovernance(
|
|
654
|
+
'test-org',
|
|
655
|
+
{
|
|
656
|
+
version: '1.0.0',
|
|
657
|
+
workflows: [createGovernedWorkflow(resourceWithoutActions)]
|
|
658
|
+
},
|
|
659
|
+
createModel([resourceWithoutActions], [systemA], { ontology: validOntology }),
|
|
660
|
+
{ mode: 'strict' }
|
|
661
|
+
)
|
|
662
|
+
).toThrow("Resource 'lead-import' declares ontology bindings but no ontology actions")
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
it('reports dangling required topology refs', () => {
|
|
666
|
+
expect(() =>
|
|
667
|
+
validateResourceGovernance(
|
|
668
|
+
'test-org',
|
|
669
|
+
{
|
|
670
|
+
version: '1.0.0',
|
|
671
|
+
workflows: [createGovernedWorkflow()]
|
|
672
|
+
},
|
|
673
|
+
createModel([workflowResource], [systemA], {
|
|
674
|
+
topology: {
|
|
675
|
+
version: 1,
|
|
676
|
+
relationships: {
|
|
677
|
+
'missing-trigger-starts-import': {
|
|
678
|
+
from: { kind: 'trigger', id: 'missing-trigger' },
|
|
679
|
+
kind: 'triggers',
|
|
680
|
+
to: { kind: 'resource', id: 'lead-import' },
|
|
681
|
+
required: true
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}),
|
|
686
|
+
{ mode: 'strict' }
|
|
687
|
+
)
|
|
688
|
+
).toThrow("Topology relationship 'missing-trigger-starts-import' from references missing trigger 'missing-trigger'")
|
|
689
|
+
})
|
|
505
690
|
|
|
506
691
|
it('reports active OM resources that reference missing Systems', () => {
|
|
507
692
|
const resourceWithMissingSystem: ResourceEntry = {
|
|
@@ -12,10 +12,8 @@
|
|
|
12
12
|
import type { WorkflowDefinition } from '../../execution/engine/workflow/types'
|
|
13
13
|
import type { AgentDefinition } from '../../execution/engine/agent/core/types'
|
|
14
14
|
import type {
|
|
15
|
-
OrganizationModel
|
|
16
|
-
|
|
17
|
-
OrganizationModelSystems
|
|
18
|
-
} from '../../organization-model/types'
|
|
15
|
+
OrganizationModel
|
|
16
|
+
} from '../../organization-model/types'
|
|
19
17
|
import type { ResourceEntry } from '../../organization-model/domains/resources'
|
|
20
18
|
import type { SystemEntry } from '../../organization-model/domains/systems'
|
|
21
19
|
import { listAllSystems } from '../../organization-model/helpers'
|
|
@@ -106,13 +104,15 @@ export interface SystemConfig {
|
|
|
106
104
|
* Used by ResourceRegistry for discovery and Command View for visualization.
|
|
107
105
|
*/
|
|
108
106
|
export interface DeploymentSpec {
|
|
109
|
-
/** Deployment version (semver) */
|
|
110
|
-
version: string
|
|
111
|
-
/** Optional Organization Model governance catalog used for OM-code validation */
|
|
112
|
-
organizationModel?:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
/** Deployment version (semver) */
|
|
108
|
+
version: string
|
|
109
|
+
/** Optional Organization Model governance catalog used for OM-code validation */
|
|
110
|
+
organizationModel?: Partial<
|
|
111
|
+
Pick<
|
|
112
|
+
OrganizationModel,
|
|
113
|
+
'systems' | 'resources' | 'ontology' | 'topology' | 'roles' | 'policies' | 'entities' | 'actions'
|
|
114
|
+
>
|
|
115
|
+
>
|
|
116
116
|
/** Workflow definitions */
|
|
117
117
|
workflows?: WorkflowDefinition[]
|
|
118
118
|
/** Agent definitions */
|
|
@@ -10,9 +10,11 @@ import type { ModelConfig } from '../../execution/engine/llm/model-info'
|
|
|
10
10
|
import { validateModelConfig, ModelConfigError } from '../../execution/engine/llm/errors'
|
|
11
11
|
import type { DeploymentSpec } from './resource-registry'
|
|
12
12
|
import type { ResourceEntry } from '../../organization-model/domains/resources'
|
|
13
|
-
import type { SystemEntry } from '../../organization-model/domains/systems'
|
|
14
|
-
import type { OrganizationModel } from '../../organization-model/types'
|
|
15
|
-
import { listAllSystems } from '../../organization-model/helpers'
|
|
13
|
+
import type { SystemEntry } from '../../organization-model/domains/systems'
|
|
14
|
+
import type { OrganizationModel } from '../../organization-model/types'
|
|
15
|
+
import { listAllSystems } from '../../organization-model/helpers'
|
|
16
|
+
import { compileOrganizationOntology, type OntologyKind, type ResolvedOntologyIndex } from '../../organization-model/ontology'
|
|
17
|
+
import type { OmTopologyNodeRef } from '../../organization-model/domains/topology'
|
|
16
18
|
import type {
|
|
17
19
|
TriggerDefinition,
|
|
18
20
|
ResourceRelationships,
|
|
@@ -44,13 +46,24 @@ export type ResourceGovernanceValidationIssueType =
|
|
|
44
46
|
| 'missing-om-resource'
|
|
45
47
|
| 'type-mismatch'
|
|
46
48
|
| 'system-mismatch'
|
|
47
|
-
| 'missing-om-system'
|
|
48
|
-
| 'raw-resource-id'
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
| 'missing-om-system'
|
|
50
|
+
| 'raw-resource-id'
|
|
51
|
+
| 'descriptor-mismatch'
|
|
52
|
+
| 'missing-ontology-actions'
|
|
53
|
+
| 'ontology-reference-missing'
|
|
54
|
+
| 'primary-action-mismatch'
|
|
55
|
+
| 'topology-reference-missing'
|
|
56
|
+
|
|
57
|
+
export interface ResourceGovernanceModel {
|
|
58
|
+
systems?: Record<string, SystemEntry>
|
|
59
|
+
resources?: Record<string, ResourceEntry>
|
|
60
|
+
ontology?: OrganizationModel['ontology']
|
|
61
|
+
topology?: OrganizationModel['topology']
|
|
62
|
+
roles?: OrganizationModel['roles']
|
|
63
|
+
policies?: OrganizationModel['policies']
|
|
64
|
+
entities?: OrganizationModel['entities']
|
|
65
|
+
actions?: OrganizationModel['actions']
|
|
66
|
+
}
|
|
54
67
|
|
|
55
68
|
export interface ResourceGovernanceValidationIssue {
|
|
56
69
|
type: ResourceGovernanceValidationIssueType
|
|
@@ -109,7 +122,7 @@ function emitGovernanceIssues(
|
|
|
109
122
|
}
|
|
110
123
|
}
|
|
111
124
|
|
|
112
|
-
function getRuntimeResources(resources: DeploymentSpec): Array<{
|
|
125
|
+
function getRuntimeResources(resources: DeploymentSpec): Array<{
|
|
113
126
|
resourceId: string
|
|
114
127
|
type: ResourceType
|
|
115
128
|
descriptor?: ResourceEntry
|
|
@@ -130,8 +143,159 @@ function getRuntimeResources(resources: DeploymentSpec): Array<{
|
|
|
130
143
|
type: integration.type,
|
|
131
144
|
descriptor: (integration as { resource?: ResourceEntry }).resource
|
|
132
145
|
}))
|
|
133
|
-
]
|
|
134
|
-
}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function hasOntologySources(organizationModel: ResourceGovernanceModel): boolean {
|
|
150
|
+
return (
|
|
151
|
+
organizationModel.ontology !== undefined ||
|
|
152
|
+
organizationModel.entities !== undefined ||
|
|
153
|
+
organizationModel.actions !== undefined ||
|
|
154
|
+
Object.values(organizationModel.systems ?? {}).some(systemHasOntologySource)
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function systemHasOntologySource(system: SystemEntry): boolean {
|
|
159
|
+
if (system.ontology !== undefined || (system.content !== undefined && Object.keys(system.content).length > 0)) return true
|
|
160
|
+
return Object.values(system.systems ?? system.subsystems ?? {}).some(systemHasOntologySource)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function ontologyIndexForKind(index: ResolvedOntologyIndex, kind: OntologyKind): Record<string, unknown> {
|
|
164
|
+
switch (kind) {
|
|
165
|
+
case 'object':
|
|
166
|
+
return index.objectTypes
|
|
167
|
+
case 'link':
|
|
168
|
+
return index.linkTypes
|
|
169
|
+
case 'action':
|
|
170
|
+
return index.actionTypes
|
|
171
|
+
case 'catalog':
|
|
172
|
+
return index.catalogTypes
|
|
173
|
+
case 'event':
|
|
174
|
+
return index.eventTypes
|
|
175
|
+
case 'interface':
|
|
176
|
+
return index.interfaceTypes
|
|
177
|
+
case 'value-type':
|
|
178
|
+
return index.valueTypes
|
|
179
|
+
case 'property':
|
|
180
|
+
return index.sharedProperties
|
|
181
|
+
case 'group':
|
|
182
|
+
return index.groups
|
|
183
|
+
case 'surface':
|
|
184
|
+
return index.surfaces
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function sameJson(left: unknown, right: unknown): boolean {
|
|
189
|
+
return JSON.stringify(left ?? null) === JSON.stringify(right ?? null)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function addOntologyBindingIssues(
|
|
193
|
+
issues: ResourceGovernanceValidationIssue[],
|
|
194
|
+
orgName: string,
|
|
195
|
+
resource: ResourceEntry,
|
|
196
|
+
ontologyIndex: ResolvedOntologyIndex | undefined
|
|
197
|
+
): void {
|
|
198
|
+
const binding = resource.ontology
|
|
199
|
+
if (binding === undefined) return
|
|
200
|
+
|
|
201
|
+
if ((resource.kind === 'workflow' || resource.kind === 'agent') && (binding.actions?.length ?? 0) === 0) {
|
|
202
|
+
addGovernanceIssue(
|
|
203
|
+
issues,
|
|
204
|
+
'missing-ontology-actions',
|
|
205
|
+
orgName,
|
|
206
|
+
resource.id,
|
|
207
|
+
`[${orgName}] Resource '${resource.id}' declares ontology bindings but no ontology actions.`
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (binding.primaryAction !== undefined && !binding.actions?.includes(binding.primaryAction)) {
|
|
212
|
+
addGovernanceIssue(
|
|
213
|
+
issues,
|
|
214
|
+
'primary-action-mismatch',
|
|
215
|
+
orgName,
|
|
216
|
+
resource.id,
|
|
217
|
+
`[${orgName}] Resource '${resource.id}' primaryAction '${binding.primaryAction}' must be included in ontology.actions.`
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (ontologyIndex === undefined) return
|
|
222
|
+
|
|
223
|
+
const validateRefs = (
|
|
224
|
+
bindingKey: 'actions' | 'primaryAction' | 'reads' | 'writes' | 'usesCatalogs' | 'emits',
|
|
225
|
+
expectedKind: OntologyKind,
|
|
226
|
+
refs: string[] | string | undefined
|
|
227
|
+
) => {
|
|
228
|
+
const values = refs === undefined ? [] : Array.isArray(refs) ? refs : [refs]
|
|
229
|
+
const index = ontologyIndexForKind(ontologyIndex, expectedKind)
|
|
230
|
+
|
|
231
|
+
for (const ref of values) {
|
|
232
|
+
if (index[ref] !== undefined) continue
|
|
233
|
+
addGovernanceIssue(
|
|
234
|
+
issues,
|
|
235
|
+
'ontology-reference-missing',
|
|
236
|
+
orgName,
|
|
237
|
+
resource.id,
|
|
238
|
+
`[${orgName}] Resource '${resource.id}' ontology.${bindingKey} references missing ${expectedKind} ontology record '${ref}'.`
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
validateRefs('actions', 'action', binding.actions)
|
|
244
|
+
validateRefs('primaryAction', 'action', binding.primaryAction)
|
|
245
|
+
validateRefs('reads', 'object', binding.reads)
|
|
246
|
+
validateRefs('writes', 'object', binding.writes)
|
|
247
|
+
validateRefs('usesCatalogs', 'catalog', binding.usesCatalogs)
|
|
248
|
+
validateRefs('emits', 'event', binding.emits)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function addTopologyIssues(
|
|
252
|
+
issues: ResourceGovernanceValidationIssue[],
|
|
253
|
+
orgName: string,
|
|
254
|
+
deployment: DeploymentSpec,
|
|
255
|
+
organizationModel: ResourceGovernanceModel,
|
|
256
|
+
systemsById: Map<string, SystemEntry>,
|
|
257
|
+
omResourcesById: Map<string, ResourceEntry>,
|
|
258
|
+
ontologyIndex: ResolvedOntologyIndex | undefined
|
|
259
|
+
): void {
|
|
260
|
+
const relationships = organizationModel.topology?.relationships
|
|
261
|
+
if (relationships === undefined) return
|
|
262
|
+
|
|
263
|
+
const rolesById = new Set(Object.keys(organizationModel.roles ?? {}))
|
|
264
|
+
const policiesById = new Set(Object.keys(organizationModel.policies ?? {}))
|
|
265
|
+
const triggerIds = new Set(deployment.triggers?.map((trigger) => trigger.resourceId) ?? [])
|
|
266
|
+
const humanCheckpointIds = new Set(deployment.humanCheckpoints?.map((checkpoint) => checkpoint.resourceId) ?? [])
|
|
267
|
+
const externalResourceIds = new Set(deployment.externalResources?.map((external) => external.resourceId) ?? [])
|
|
268
|
+
|
|
269
|
+
const topologyRefExists = (ref: OmTopologyNodeRef): boolean => {
|
|
270
|
+
if (ref.kind === 'system') return systemsById.has(ref.id)
|
|
271
|
+
if (ref.kind === 'resource') return omResourcesById.has(ref.id)
|
|
272
|
+
if (ref.kind === 'policy') return policiesById.has(ref.id)
|
|
273
|
+
if (ref.kind === 'role') return rolesById.has(ref.id)
|
|
274
|
+
if (ref.kind === 'trigger') return triggerIds.has(ref.id)
|
|
275
|
+
if (ref.kind === 'humanCheckpoint') return humanCheckpointIds.has(ref.id)
|
|
276
|
+
if (ref.kind === 'externalResource') return externalResourceIds.has(ref.id)
|
|
277
|
+
if (ref.kind === 'ontology') {
|
|
278
|
+
if (ontologyIndex === undefined) return true
|
|
279
|
+
return Object.values(ontologyIndex).some((records) => records[ref.id] !== undefined)
|
|
280
|
+
}
|
|
281
|
+
return false
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for (const [relationshipId, relationship] of Object.entries(relationships)) {
|
|
285
|
+
;(['from', 'to'] as const).forEach((side) => {
|
|
286
|
+
const ref = relationship[side]
|
|
287
|
+
if (topologyRefExists(ref)) return
|
|
288
|
+
|
|
289
|
+
addGovernanceIssue(
|
|
290
|
+
issues,
|
|
291
|
+
'topology-reference-missing',
|
|
292
|
+
orgName,
|
|
293
|
+
ref.id,
|
|
294
|
+
`[${orgName}] Topology relationship '${relationshipId}' ${side} references missing ${ref.kind} '${ref.id}'.`
|
|
295
|
+
)
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
}
|
|
135
299
|
|
|
136
300
|
/**
|
|
137
301
|
* Validates runtime resource definitions against OM Resources and Systems.
|
|
@@ -147,9 +311,9 @@ export function validateResourceGovernance(
|
|
|
147
311
|
options: ResourceGovernanceValidationOptions = {}
|
|
148
312
|
): ResourceGovernanceValidationResult {
|
|
149
313
|
const mode = getResourceValidatorMode(options.mode)
|
|
150
|
-
const omResourcesMap = organizationModel?.resources
|
|
151
|
-
const omSystemsMap = organizationModel?.systems
|
|
152
|
-
const issues: ResourceGovernanceValidationIssue[] = []
|
|
314
|
+
const omResourcesMap = organizationModel?.resources
|
|
315
|
+
const omSystemsMap = organizationModel?.systems
|
|
316
|
+
const issues: ResourceGovernanceValidationIssue[] = []
|
|
153
317
|
|
|
154
318
|
if (!omResourcesMap || !omSystemsMap) {
|
|
155
319
|
return { valid: true, mode, issues }
|
|
@@ -160,12 +324,26 @@ export function validateResourceGovernance(
|
|
|
160
324
|
const systemsById = new Map(
|
|
161
325
|
listAllSystems({ systems: omSystemsMap } as OrganizationModel).map(({ path, system }) => [path, system])
|
|
162
326
|
)
|
|
163
|
-
const activeOmResources = Object.values(omResourcesMap).filter((resource) => resource.status === 'active')
|
|
164
|
-
const omResourcesById = new Map(activeOmResources.map((resource) => [resource.id, resource]))
|
|
165
|
-
const runtimeResources = getRuntimeResources(deployment)
|
|
166
|
-
const runtimeResourcesById = new Map(runtimeResources.map((resource) => [resource.resourceId, resource]))
|
|
167
|
-
|
|
168
|
-
|
|
327
|
+
const activeOmResources = Object.values(omResourcesMap).filter((resource) => resource.status === 'active')
|
|
328
|
+
const omResourcesById = new Map(activeOmResources.map((resource) => [resource.id, resource]))
|
|
329
|
+
const runtimeResources = getRuntimeResources(deployment)
|
|
330
|
+
const runtimeResourcesById = new Map(runtimeResources.map((resource) => [resource.resourceId, resource]))
|
|
331
|
+
const ontologyCompilation = hasOntologySources(organizationModel)
|
|
332
|
+
? compileOrganizationOntology(organizationModel)
|
|
333
|
+
: undefined
|
|
334
|
+
const ontologyIndex = ontologyCompilation?.ontology
|
|
335
|
+
|
|
336
|
+
for (const diagnostic of ontologyCompilation?.diagnostics ?? []) {
|
|
337
|
+
addGovernanceIssue(
|
|
338
|
+
issues,
|
|
339
|
+
'ontology-reference-missing',
|
|
340
|
+
orgName,
|
|
341
|
+
diagnostic.id,
|
|
342
|
+
`[${orgName}] ${diagnostic.message}`
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
for (const resource of activeOmResources) {
|
|
169
347
|
if (!systemsById.has(resource.systemPath)) {
|
|
170
348
|
addGovernanceIssue(
|
|
171
349
|
issues,
|
|
@@ -198,16 +376,28 @@ export function validateResourceGovernance(
|
|
|
198
376
|
)
|
|
199
377
|
}
|
|
200
378
|
|
|
201
|
-
if (runtimeResource.descriptor && runtimeResource.descriptor.systemPath !== resource.systemPath) {
|
|
202
|
-
addGovernanceIssue(
|
|
379
|
+
if (runtimeResource.descriptor && runtimeResource.descriptor.systemPath !== resource.systemPath) {
|
|
380
|
+
addGovernanceIssue(
|
|
203
381
|
issues,
|
|
204
382
|
'system-mismatch',
|
|
205
383
|
orgName,
|
|
206
384
|
resource.id,
|
|
207
385
|
`[${orgName}] Resource '${resource.id}' system mismatch: code descriptor has '${runtimeResource.descriptor.systemPath}', OM has '${resource.systemPath}'.`
|
|
208
|
-
)
|
|
209
|
-
}
|
|
210
|
-
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (runtimeResource.descriptor && !sameJson(runtimeResource.descriptor.ontology, resource.ontology)) {
|
|
390
|
+
addGovernanceIssue(
|
|
391
|
+
issues,
|
|
392
|
+
'descriptor-mismatch',
|
|
393
|
+
orgName,
|
|
394
|
+
resource.id,
|
|
395
|
+
`[${orgName}] Resource '${resource.id}' ontology descriptor mismatch between code and OM.`
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
addOntologyBindingIssues(issues, orgName, resource, ontologyIndex)
|
|
400
|
+
}
|
|
211
401
|
|
|
212
402
|
for (const runtimeResource of runtimeResources) {
|
|
213
403
|
const omResource = omResourcesById.get(runtimeResource.resourceId)
|
|
@@ -242,18 +432,20 @@ export function validateResourceGovernance(
|
|
|
242
432
|
)
|
|
243
433
|
}
|
|
244
434
|
|
|
245
|
-
if (runtimeResource.descriptor.kind !== runtimeResource.type) {
|
|
246
|
-
addGovernanceIssue(
|
|
435
|
+
if (runtimeResource.descriptor.kind !== runtimeResource.type) {
|
|
436
|
+
addGovernanceIssue(
|
|
247
437
|
issues,
|
|
248
438
|
'type-mismatch',
|
|
249
439
|
orgName,
|
|
250
440
|
runtimeResource.resourceId,
|
|
251
441
|
`[${orgName}] Code-side resource '${runtimeResource.resourceId}' descriptor kind '${runtimeResource.descriptor.kind}' does not match runtime type '${runtimeResource.type}'.`
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
|
|
442
|
+
)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
addTopologyIssues(issues, orgName, deployment, organizationModel, systemsById, omResourcesById, ontologyIndex)
|
|
447
|
+
|
|
448
|
+
emitGovernanceIssues(issues, mode, options.onWarning)
|
|
257
449
|
|
|
258
450
|
return {
|
|
259
451
|
valid: issues.length === 0,
|