@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
|
@@ -31,12 +31,13 @@ const VALID_ROLE = {
|
|
|
31
31
|
const WORKFLOW_RESOURCE = {
|
|
32
32
|
id: 'LGN-01-company-scrape',
|
|
33
33
|
order: 10,
|
|
34
|
-
kind: 'workflow' as const,
|
|
35
|
-
systemPath: 'sys.lead-gen',
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
kind: 'workflow' as const,
|
|
35
|
+
systemPath: 'sys.lead-gen',
|
|
36
|
+
title: 'Company Scrape',
|
|
37
|
+
description: 'Scrapes company data for lead generation.',
|
|
38
|
+
ownerRoleId: 'role.sales-ops',
|
|
39
|
+
status: 'active' as const,
|
|
40
|
+
codeRefs: [
|
|
40
41
|
{
|
|
41
42
|
path: 'operations/src/lead-gen/company-scrape/index.ts',
|
|
42
43
|
role: 'entrypoint' as const,
|
|
@@ -231,7 +232,8 @@ describe('ResourceEntrySchema', () => {
|
|
|
231
232
|
|
|
232
233
|
it('accepts optional ontology bindings on resource variants without changing top-level event emissions', () => {
|
|
233
234
|
const ontology = {
|
|
234
|
-
|
|
235
|
+
actions: ['sys.lead-gen:action/company.scrape'],
|
|
236
|
+
primaryAction: 'sys.lead-gen:action/company.scrape',
|
|
235
237
|
reads: ['sys.lead-gen:object/company'],
|
|
236
238
|
writes: ['sys.lead-gen:object/company'],
|
|
237
239
|
usesCatalogs: ['sys.lead-gen:catalog/build-template'],
|
|
@@ -268,7 +270,16 @@ describe('ResourceEntrySchema', () => {
|
|
|
268
270
|
it('rejects malformed ontology binding ids', () => {
|
|
269
271
|
expect(
|
|
270
272
|
ResourceOntologyBindingSchema.safeParse({
|
|
271
|
-
|
|
273
|
+
actions: ['sys.lead-gen/action/company.scrape']
|
|
274
|
+
}).success
|
|
275
|
+
).toBe(false)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('rejects primaryAction values outside actions', () => {
|
|
279
|
+
expect(
|
|
280
|
+
ResourceOntologyBindingSchema.safeParse({
|
|
281
|
+
actions: ['sys.lead-gen:action/company.scrape'],
|
|
282
|
+
primaryAction: 'sys.lead-gen:action/company.qualify'
|
|
272
283
|
}).success
|
|
273
284
|
).toBe(false)
|
|
274
285
|
})
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { ZodError } from 'zod'
|
|
3
|
+
import {
|
|
4
|
+
OmTopologyDomainSchema,
|
|
5
|
+
OmTopologyMetadataSchema,
|
|
6
|
+
defineTopology,
|
|
7
|
+
defineTopologyRelationship,
|
|
8
|
+
parseTopologyNodeRef,
|
|
9
|
+
topologyRef
|
|
10
|
+
} from '../../domains/topology'
|
|
11
|
+
import { resolveOrganizationModel } from '../../resolve'
|
|
12
|
+
|
|
13
|
+
const VALID_SYSTEM = {
|
|
14
|
+
id: 'sales',
|
|
15
|
+
order: 10,
|
|
16
|
+
label: 'Sales',
|
|
17
|
+
enabled: true,
|
|
18
|
+
lifecycle: 'active' as const
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SOURCE_RESOURCE = {
|
|
22
|
+
id: 'email-discovery',
|
|
23
|
+
order: 10,
|
|
24
|
+
kind: 'workflow' as const,
|
|
25
|
+
systemPath: 'sales',
|
|
26
|
+
status: 'active' as const
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const TARGET_RESOURCE = {
|
|
30
|
+
id: 'email-verification',
|
|
31
|
+
order: 20,
|
|
32
|
+
kind: 'workflow' as const,
|
|
33
|
+
systemPath: 'sales',
|
|
34
|
+
status: 'active' as const
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('OM topology domain', () => {
|
|
38
|
+
it('accepts first-class topology relationships', () => {
|
|
39
|
+
const model = resolveOrganizationModel({
|
|
40
|
+
systems: { sales: VALID_SYSTEM },
|
|
41
|
+
resources: {
|
|
42
|
+
'email-discovery': SOURCE_RESOURCE,
|
|
43
|
+
'email-verification': TARGET_RESOURCE
|
|
44
|
+
},
|
|
45
|
+
topology: {
|
|
46
|
+
version: 1,
|
|
47
|
+
relationships: {
|
|
48
|
+
'email-discovery-triggers-email-verification': {
|
|
49
|
+
from: { kind: 'resource', id: 'email-discovery' },
|
|
50
|
+
kind: 'triggers',
|
|
51
|
+
to: { kind: 'resource', id: 'email-verification' },
|
|
52
|
+
systemPath: 'sales',
|
|
53
|
+
required: true,
|
|
54
|
+
metadata: { source: 'authored' }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
expect(model.topology.relationships['email-discovery-triggers-email-verification']).toMatchObject({
|
|
61
|
+
from: { kind: 'resource', id: 'email-discovery' },
|
|
62
|
+
kind: 'triggers',
|
|
63
|
+
to: { kind: 'resource', id: 'email-verification' },
|
|
64
|
+
required: true
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('rejects dangling first-class topology refs', () => {
|
|
69
|
+
expect(() =>
|
|
70
|
+
resolveOrganizationModel({
|
|
71
|
+
systems: { sales: VALID_SYSTEM },
|
|
72
|
+
resources: { 'email-discovery': SOURCE_RESOURCE },
|
|
73
|
+
topology: {
|
|
74
|
+
version: 1,
|
|
75
|
+
relationships: {
|
|
76
|
+
'email-discovery-triggers-missing': {
|
|
77
|
+
from: { kind: 'resource', id: 'email-discovery' },
|
|
78
|
+
kind: 'triggers',
|
|
79
|
+
to: { kind: 'resource', id: 'missing-resource' }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
).toThrow(/unknown resource \\"missing-resource\\"/)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('reports dangling topology refs on the relationship side path', () => {
|
|
88
|
+
let error: unknown
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
resolveOrganizationModel({
|
|
92
|
+
systems: { sales: VALID_SYSTEM },
|
|
93
|
+
resources: { 'email-discovery': SOURCE_RESOURCE },
|
|
94
|
+
topology: {
|
|
95
|
+
version: 1,
|
|
96
|
+
relationships: {
|
|
97
|
+
'email-discovery-triggers-missing': {
|
|
98
|
+
from: { kind: 'resource', id: 'email-discovery' },
|
|
99
|
+
kind: 'triggers',
|
|
100
|
+
to: { kind: 'resource', id: 'missing-resource' }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
} catch (caught) {
|
|
106
|
+
error = caught
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
expect(error).toBeInstanceOf(ZodError)
|
|
110
|
+
if (!(error instanceof ZodError)) {
|
|
111
|
+
throw new Error('Expected resolveOrganizationModel to throw a ZodError')
|
|
112
|
+
}
|
|
113
|
+
expect(error.issues).toEqual(
|
|
114
|
+
expect.arrayContaining([
|
|
115
|
+
expect.objectContaining({
|
|
116
|
+
path: ['topology', 'relationships', 'email-discovery-triggers-missing', 'to']
|
|
117
|
+
})
|
|
118
|
+
])
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('rejects secret-like topology metadata', () => {
|
|
123
|
+
expect(
|
|
124
|
+
OmTopologyMetadataSchema.safeParse({
|
|
125
|
+
owner: 'sales-ops',
|
|
126
|
+
credentials: { apiKey: 'sk-test-12345678901234567890' }
|
|
127
|
+
}).success
|
|
128
|
+
).toBe(false)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('compiles typed resource objects through authoring helpers', () => {
|
|
132
|
+
const relationship = defineTopologyRelationship({
|
|
133
|
+
from: SOURCE_RESOURCE,
|
|
134
|
+
kind: 'uses',
|
|
135
|
+
to: topologyRef.externalResource('apollo'),
|
|
136
|
+
required: true
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
expect(relationship).toEqual({
|
|
140
|
+
from: { kind: 'resource', id: 'email-discovery' },
|
|
141
|
+
kind: 'uses',
|
|
142
|
+
to: { kind: 'externalResource', id: 'apollo' },
|
|
143
|
+
required: true
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
expect(
|
|
147
|
+
defineTopology({
|
|
148
|
+
'email-discovery-uses-apollo': {
|
|
149
|
+
from: SOURCE_RESOURCE,
|
|
150
|
+
kind: 'uses',
|
|
151
|
+
to: topologyRef.externalResource('apollo')
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
).toMatchObject({
|
|
155
|
+
version: 1,
|
|
156
|
+
relationships: {
|
|
157
|
+
'email-discovery-uses-apollo': {
|
|
158
|
+
from: { kind: 'resource', id: 'email-discovery' },
|
|
159
|
+
to: { kind: 'externalResource', id: 'apollo' }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('parses boundary string refs without making raw strings canonical', () => {
|
|
166
|
+
expect(parseTopologyNodeRef('resource:email-discovery')).toEqual({
|
|
167
|
+
kind: 'resource',
|
|
168
|
+
id: 'email-discovery'
|
|
169
|
+
})
|
|
170
|
+
expect(parseTopologyNodeRef('ontology:sales:action/contact.verify-email')).toEqual({
|
|
171
|
+
kind: 'ontology',
|
|
172
|
+
id: 'sales:action/contact.verify-email'
|
|
173
|
+
})
|
|
174
|
+
expect(() => parseTopologyNodeRef('email-discovery')).toThrow(/must use <kind>:<id>/)
|
|
175
|
+
expect(() =>
|
|
176
|
+
OmTopologyDomainSchema.parse({
|
|
177
|
+
version: 1,
|
|
178
|
+
relationships: {
|
|
179
|
+
invalid: {
|
|
180
|
+
from: 'resource:email-discovery',
|
|
181
|
+
kind: 'uses',
|
|
182
|
+
to: { kind: 'resource', id: 'email-verification' }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
).toThrow()
|
|
187
|
+
})
|
|
188
|
+
})
|
|
@@ -115,15 +115,16 @@ describe('organization graph', () => {
|
|
|
115
115
|
expect(() => OrganizationGraphEdgeKindSchema.parse('links')).not.toThrow()
|
|
116
116
|
expect(() => OrganizationGraphEdgeKindSchema.parse('affects')).not.toThrow()
|
|
117
117
|
expect(() => OrganizationGraphEdgeKindSchema.parse('emits')).not.toThrow()
|
|
118
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse('originates_from')).not.toThrow()
|
|
119
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse('triggers')).not.toThrow()
|
|
118
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('originates_from')).not.toThrow()
|
|
119
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('triggers')).not.toThrow()
|
|
120
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('approval')).not.toThrow()
|
|
120
121
|
expect(() => OrganizationGraphNodeKindSchema.parse('surface')).not.toThrow()
|
|
121
122
|
expect(() => OrganizationGraphNodeKindSchema.parse('customer-segment')).not.toThrow()
|
|
122
123
|
expect(() => OrganizationGraphNodeKindSchema.parse('offering')).not.toThrow()
|
|
123
124
|
expect(() => OrganizationGraphNodeKindSchema.parse('goal')).not.toThrow()
|
|
124
125
|
expect(() => OrganizationGraphNodeKindSchema.parse('navigation-group')).not.toThrow()
|
|
125
126
|
expect(() => OrganizationGraphNodeKindSchema.parse('ontology')).not.toThrow()
|
|
126
|
-
expect(() => OrganizationGraphEdgeKindSchema.parse('
|
|
127
|
+
expect(() => OrganizationGraphEdgeKindSchema.parse('actions')).not.toThrow()
|
|
127
128
|
expect(() => OrganizationGraphEdgeKindSchema.parse('reads')).not.toThrow()
|
|
128
129
|
expect(() => OrganizationGraphEdgeKindSchema.parse('writes')).not.toThrow()
|
|
129
130
|
expect(() => OrganizationGraphEdgeKindSchema.parse('uses_catalog')).not.toThrow()
|
|
@@ -431,7 +432,8 @@ describe('organization graph', () => {
|
|
|
431
432
|
systemPath: 'test.bindings',
|
|
432
433
|
status: 'active',
|
|
433
434
|
ontology: {
|
|
434
|
-
|
|
435
|
+
actions: ['test.bindings:action/update-deal'],
|
|
436
|
+
primaryAction: 'test.bindings:action/update-deal',
|
|
435
437
|
reads: ['test.bindings:object/deal'],
|
|
436
438
|
writes: ['test.bindings:object/deal'],
|
|
437
439
|
usesCatalogs: ['test.bindings:catalog/pipeline'],
|
|
@@ -445,7 +447,7 @@ describe('organization graph', () => {
|
|
|
445
447
|
expect(
|
|
446
448
|
graph.edges.find(
|
|
447
449
|
(edge) =>
|
|
448
|
-
edge.kind === '
|
|
450
|
+
edge.kind === 'actions' &&
|
|
449
451
|
edge.sourceId === 'resource:deal-workflow' &&
|
|
450
452
|
edge.targetId === 'ontology:test.bindings:action/update-deal'
|
|
451
453
|
)
|
|
@@ -483,8 +485,97 @@ describe('organization graph', () => {
|
|
|
483
485
|
)
|
|
484
486
|
).toBeDefined()
|
|
485
487
|
})
|
|
486
|
-
|
|
487
|
-
it('projects
|
|
488
|
+
|
|
489
|
+
it('projects topology relationships without collapsing ontology binding edges', () => {
|
|
490
|
+
const model = resolveOrganizationModel({
|
|
491
|
+
systems: {
|
|
492
|
+
'test.topology': {
|
|
493
|
+
id: 'test.topology',
|
|
494
|
+
order: 10,
|
|
495
|
+
label: 'Topology System',
|
|
496
|
+
enabled: true,
|
|
497
|
+
ontology: {
|
|
498
|
+
actionTypes: {
|
|
499
|
+
'test.topology:action/discover-email': {
|
|
500
|
+
id: 'test.topology:action/discover-email',
|
|
501
|
+
label: 'Discover Email'
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
resources: {
|
|
508
|
+
'email-discovery': {
|
|
509
|
+
id: 'email-discovery',
|
|
510
|
+
order: 10,
|
|
511
|
+
kind: 'workflow',
|
|
512
|
+
systemPath: 'test.topology',
|
|
513
|
+
status: 'active',
|
|
514
|
+
ontology: {
|
|
515
|
+
actions: ['test.topology:action/discover-email'],
|
|
516
|
+
primaryAction: 'test.topology:action/discover-email'
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
'email-verification': {
|
|
520
|
+
id: 'email-verification',
|
|
521
|
+
order: 20,
|
|
522
|
+
kind: 'workflow',
|
|
523
|
+
systemPath: 'test.topology',
|
|
524
|
+
status: 'active'
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
topology: {
|
|
528
|
+
version: 1,
|
|
529
|
+
relationships: {
|
|
530
|
+
'email-discovery-triggers-email-verification': {
|
|
531
|
+
from: { kind: 'resource', id: 'email-discovery' },
|
|
532
|
+
kind: 'triggers',
|
|
533
|
+
to: { kind: 'resource', id: 'email-verification' },
|
|
534
|
+
required: true
|
|
535
|
+
},
|
|
536
|
+
'email-verification-approval-human-review': {
|
|
537
|
+
from: { kind: 'resource', id: 'email-verification' },
|
|
538
|
+
kind: 'approval',
|
|
539
|
+
to: { kind: 'humanCheckpoint', id: 'email-review' }
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
})
|
|
544
|
+
const graph = buildOrganizationGraph({ organizationModel: model })
|
|
545
|
+
|
|
546
|
+
expect(
|
|
547
|
+
graph.edges.find(
|
|
548
|
+
(edge) =>
|
|
549
|
+
edge.kind === 'actions' &&
|
|
550
|
+
edge.sourceId === 'resource:email-discovery' &&
|
|
551
|
+
edge.targetId === 'ontology:test.topology:action/discover-email'
|
|
552
|
+
)
|
|
553
|
+
).toBeDefined()
|
|
554
|
+
expect(
|
|
555
|
+
graph.edges.find(
|
|
556
|
+
(edge) =>
|
|
557
|
+
edge.kind === 'triggers' &&
|
|
558
|
+
edge.relationshipType === 'triggers' &&
|
|
559
|
+
edge.sourceId === 'resource:email-discovery' &&
|
|
560
|
+
edge.targetId === 'resource:email-verification'
|
|
561
|
+
)
|
|
562
|
+
).toBeDefined()
|
|
563
|
+
expect(graph.nodes.find((node) => node.id === 'resource:email-review')).toMatchObject({
|
|
564
|
+
kind: 'resource',
|
|
565
|
+
resourceType: 'human_checkpoint'
|
|
566
|
+
})
|
|
567
|
+
expect(
|
|
568
|
+
graph.edges.find(
|
|
569
|
+
(edge) =>
|
|
570
|
+
edge.kind === 'approval' &&
|
|
571
|
+
edge.relationshipType === 'approval' &&
|
|
572
|
+
edge.sourceId === 'resource:email-verification' &&
|
|
573
|
+
edge.targetId === 'resource:email-review'
|
|
574
|
+
)
|
|
575
|
+
).toBeDefined()
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
it('projects surface and navigation-group nodes from recursive sidebar leaves', () => {
|
|
488
579
|
const model = resolveOrganizationModel({
|
|
489
580
|
navigation: {
|
|
490
581
|
sidebar: {
|
|
@@ -333,9 +333,17 @@ describe('ontology contract validation', () => {
|
|
|
333
333
|
const compilation = compileOrganizationOntology(model)
|
|
334
334
|
|
|
335
335
|
expect(compilation.diagnostics).toEqual([])
|
|
336
|
-
expect(compilation.ontology.objectTypes['dashboard:object/crm.deal']
|
|
336
|
+
expect(compilation.ontology.objectTypes['dashboard:object/crm.deal']).not.toHaveProperty('legacyEntityId')
|
|
337
|
+
expect(compilation.ontology.objectTypes['dashboard:object/crm.deal']?.origin).toMatchObject({
|
|
338
|
+
source: 'legacy.entities',
|
|
339
|
+
legacyId: 'crm.deal'
|
|
340
|
+
})
|
|
337
341
|
expect(compilation.ontology.actionTypes['dashboard:action/send_reply']?.legacyActionId).toBe('send_reply')
|
|
338
|
-
expect(compilation.ontology.catalogTypes['dashboard:catalog/pipeline']
|
|
342
|
+
expect(compilation.ontology.catalogTypes['dashboard:catalog/pipeline']).not.toHaveProperty('legacyContentId')
|
|
343
|
+
expect(compilation.ontology.catalogTypes['dashboard:catalog/pipeline']?.origin).toMatchObject({
|
|
344
|
+
source: 'legacy.system.content',
|
|
345
|
+
legacyId: 'dashboard:pipeline'
|
|
346
|
+
})
|
|
339
347
|
})
|
|
340
348
|
|
|
341
349
|
it('adds origin metadata to authored and projected compiled ontology records without mutating source', () => {
|
|
@@ -477,7 +485,8 @@ describe('ontology contract validation', () => {
|
|
|
477
485
|
systemPath: 'dashboard',
|
|
478
486
|
status: 'active',
|
|
479
487
|
ontology: {
|
|
480
|
-
|
|
488
|
+
actions: ['dashboard:action/process-task'],
|
|
489
|
+
primaryAction: 'dashboard:action/process-task',
|
|
481
490
|
reads: ['dashboard:object/task'],
|
|
482
491
|
writes: ['dashboard:object/task'],
|
|
483
492
|
usesCatalogs: ['dashboard:catalog/task-status'],
|
|
@@ -605,7 +614,8 @@ describe('ontology contract validation', () => {
|
|
|
605
614
|
systemPath: 'dashboard',
|
|
606
615
|
status: 'active',
|
|
607
616
|
ontology: {
|
|
608
|
-
|
|
617
|
+
actions: ['dashboard:action/missing-task'],
|
|
618
|
+
primaryAction: 'dashboard:action/missing-task',
|
|
609
619
|
reads: ['dashboard:object/missing-task'],
|
|
610
620
|
usesCatalogs: ['dashboard:catalog/missing-status'],
|
|
611
621
|
emits: ['dashboard:event/missing-event']
|
|
@@ -17,6 +17,7 @@ import { DEFAULT_ORGANIZATION_MODEL_OFFERINGS } from './domains/offerings'
|
|
|
17
17
|
import { DEFAULT_ORGANIZATION_MODEL_ROLES } from './domains/roles'
|
|
18
18
|
import { DEFAULT_ORGANIZATION_MODEL_GOALS } from './domains/goals'
|
|
19
19
|
import { DEFAULT_ORGANIZATION_MODEL_RESOURCES } from './domains/resources'
|
|
20
|
+
import { DEFAULT_ORGANIZATION_MODEL_TOPOLOGY } from './domains/topology'
|
|
20
21
|
import { CRM_ACTION_ENTRIES, DEFAULT_ORGANIZATION_MODEL_ACTIONS, LEAD_GEN_ACTION_ENTRIES } from './domains/actions'
|
|
21
22
|
import { DEFAULT_ORGANIZATION_MODEL_ENTITIES as DEFAULT_ENTITIES } from './domains/entities'
|
|
22
23
|
import { DEFAULT_ORGANIZATION_MODEL_POLICIES } from './domains/policies'
|
|
@@ -741,6 +742,7 @@ export const DEFAULT_ORGANIZATION_MODEL: OrganizationModel = {
|
|
|
741
742
|
},
|
|
742
743
|
ontology: DEFAULT_ONTOLOGY_SCOPE,
|
|
743
744
|
resources: DEFAULT_ORGANIZATION_MODEL_RESOURCES,
|
|
745
|
+
topology: DEFAULT_ORGANIZATION_MODEL_TOPOLOGY,
|
|
744
746
|
actions: DEFAULT_ORGANIZATION_MODEL_ACTIONS,
|
|
745
747
|
entities: DEFAULT_ORGANIZATION_MODEL_ENTITIES,
|
|
746
748
|
policies: DEFAULT_ORGANIZATION_MODEL_POLICIES,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { ModelIdSchema } from './shared'
|
|
2
|
+
import { DescriptionSchema, LabelSchema, ModelIdSchema } from './shared'
|
|
3
3
|
import { SystemPathSchema, SystemLifecycleSchema } from './systems'
|
|
4
4
|
import { ActionInvocationSchema } from './actions'
|
|
5
5
|
import { OntologyIdSchema } from '../ontology'
|
|
@@ -57,13 +57,35 @@ export const EventDescriptorSchema = EventEmissionDescriptorSchema.extend({
|
|
|
57
57
|
ownerKind: z.enum(['resource', 'entity']).meta({ label: 'Owner kind' })
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
-
export const ResourceOntologyBindingSchema = z
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
export const ResourceOntologyBindingSchema = z
|
|
61
|
+
.object({
|
|
62
|
+
actions: z.array(OntologyIdSchema).optional(),
|
|
63
|
+
primaryAction: OntologyIdSchema.optional(),
|
|
64
|
+
reads: z.array(OntologyIdSchema).optional(),
|
|
65
|
+
writes: z.array(OntologyIdSchema).optional(),
|
|
66
|
+
usesCatalogs: z.array(OntologyIdSchema).optional(),
|
|
67
|
+
emits: z.array(OntologyIdSchema).optional()
|
|
68
|
+
})
|
|
69
|
+
.superRefine((binding, ctx) => {
|
|
70
|
+
if (binding.primaryAction === undefined) return
|
|
71
|
+
if (binding.actions?.includes(binding.primaryAction)) return
|
|
72
|
+
|
|
73
|
+
ctx.addIssue({
|
|
74
|
+
code: z.ZodIssueCode.custom,
|
|
75
|
+
path: ['primaryAction'],
|
|
76
|
+
message: 'Resource ontology primaryAction must be included in actions'
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
type OntologyRefInput = string | { id: string }
|
|
81
|
+
type ResourceOntologyBindingInput = {
|
|
82
|
+
actions?: OntologyRefInput[]
|
|
83
|
+
primaryAction?: OntologyRefInput
|
|
84
|
+
reads?: OntologyRefInput[]
|
|
85
|
+
writes?: OntologyRefInput[]
|
|
86
|
+
usesCatalogs?: OntologyRefInput[]
|
|
87
|
+
emits?: OntologyRefInput[]
|
|
88
|
+
}
|
|
67
89
|
|
|
68
90
|
export const CodeReferenceSchema = z.object({
|
|
69
91
|
path: z
|
|
@@ -83,7 +105,11 @@ const ResourceEntryBaseSchema = z.object({
|
|
|
83
105
|
/** Domain-map iteration order. Convention: multiples of 10 (10, 20, 30, ...) to allow easy insertion. */
|
|
84
106
|
order: z.number().default(0),
|
|
85
107
|
/** Required single System membership — value is a dot-separated system path (e.g. "sales.lead-gen"). */
|
|
86
|
-
systemPath: SystemPathSchema.meta({ ref: 'system' }),
|
|
108
|
+
systemPath: SystemPathSchema.meta({ ref: 'system' }),
|
|
109
|
+
/** Executable display title owned by the OM Resource descriptor. */
|
|
110
|
+
title: LabelSchema.optional(),
|
|
111
|
+
/** Executable display description owned by the OM Resource descriptor. */
|
|
112
|
+
description: DescriptionSchema.optional(),
|
|
87
113
|
/** Optional role responsible for maintaining this resource. */
|
|
88
114
|
ownerRoleId: ModelIdSchema.meta({ ref: 'role' }).optional(),
|
|
89
115
|
status: ResourceGovernanceStatusSchema,
|
|
@@ -97,12 +123,10 @@ const ResourceEntryBaseSchema = z.object({
|
|
|
97
123
|
codeRefs: z.array(CodeReferenceSchema).default([])
|
|
98
124
|
})
|
|
99
125
|
|
|
100
|
-
export const WorkflowResourceEntrySchema = ResourceEntryBaseSchema.extend({
|
|
101
|
-
kind: z.literal('workflow'),
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
emits: z.array(EventEmissionDescriptorSchema).optional()
|
|
105
|
-
})
|
|
126
|
+
export const WorkflowResourceEntrySchema = ResourceEntryBaseSchema.extend({
|
|
127
|
+
kind: z.literal('workflow'),
|
|
128
|
+
emits: z.array(EventEmissionDescriptorSchema).optional()
|
|
129
|
+
})
|
|
106
130
|
|
|
107
131
|
export const AgentResourceEntrySchema = ResourceEntryBaseSchema.extend({
|
|
108
132
|
kind: z.literal('agent'),
|
|
@@ -155,13 +179,32 @@ export function defineResource<const TResource extends ResourceEntry>(resource:
|
|
|
155
179
|
return ResourceEntrySchema.parse(resource) as TResource
|
|
156
180
|
}
|
|
157
181
|
|
|
158
|
-
export function defineResources<const TResources extends Record<string, ResourceEntry>>(
|
|
159
|
-
resources: TResources
|
|
160
|
-
): TResources {
|
|
182
|
+
export function defineResources<const TResources extends Record<string, ResourceEntry>>(
|
|
183
|
+
resources: TResources
|
|
184
|
+
): TResources {
|
|
161
185
|
return Object.fromEntries(
|
|
162
186
|
Object.entries(resources).map(([key, resource]) => [key, ResourceEntrySchema.parse(resource)])
|
|
163
|
-
) as TResources
|
|
164
|
-
}
|
|
187
|
+
) as TResources
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function ontologyIdFrom(input: OntologyRefInput): string {
|
|
191
|
+
return typeof input === 'string' ? input : input.id
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function ontologyIdArrayFrom(input: OntologyRefInput[] | undefined): string[] | undefined {
|
|
195
|
+
return input?.map(ontologyIdFrom)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function defineResourceOntology(input: ResourceOntologyBindingInput): ResourceOntologyBinding {
|
|
199
|
+
return ResourceOntologyBindingSchema.parse({
|
|
200
|
+
actions: ontologyIdArrayFrom(input.actions),
|
|
201
|
+
primaryAction: input.primaryAction === undefined ? undefined : ontologyIdFrom(input.primaryAction),
|
|
202
|
+
reads: ontologyIdArrayFrom(input.reads),
|
|
203
|
+
writes: ontologyIdArrayFrom(input.writes),
|
|
204
|
+
usesCatalogs: ontologyIdArrayFrom(input.usesCatalogs),
|
|
205
|
+
emits: ontologyIdArrayFrom(input.emits)
|
|
206
|
+
})
|
|
207
|
+
}
|
|
165
208
|
|
|
166
209
|
export type ResourceId = z.infer<typeof ResourceIdSchema>
|
|
167
210
|
export type ResourceKind = z.infer<typeof ResourceKindSchema>
|