@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
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { OntologyIdSchema, parseOntologyId } from '../ontology'
|
|
3
|
+
import { JsonValueSchema, SystemPathSchema } from './systems'
|
|
4
|
+
import { ModelIdSchema } from './shared'
|
|
5
|
+
import { ResourceIdSchema, type ResourceEntry } from './resources'
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Topology domain
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
//
|
|
11
|
+
// Topology captures durable operational wiring. It stays separate from
|
|
12
|
+
// ontology, which describes semantic meaning.
|
|
13
|
+
|
|
14
|
+
const SecretLikeMetadataKeySchema = /(?:secret|password|passwd|token|api[-_]?key|credential|private[-_]?key)/i
|
|
15
|
+
const SecretLikeMetadataValueSchema =
|
|
16
|
+
/(?:sk-[A-Za-z0-9_-]{12,}|pk_live_[A-Za-z0-9_-]{12,}|eyJ[A-Za-z0-9_-]{20,}|-----BEGIN (?:RSA |OPENSSH |EC )?PRIVATE KEY-----)/
|
|
17
|
+
|
|
18
|
+
export const OmTopologyNodeKindSchema = z.enum([
|
|
19
|
+
'system',
|
|
20
|
+
'resource',
|
|
21
|
+
'ontology',
|
|
22
|
+
'policy',
|
|
23
|
+
'role',
|
|
24
|
+
'trigger',
|
|
25
|
+
'humanCheckpoint',
|
|
26
|
+
'externalResource'
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
export const OmTopologyRelationshipKindSchema = z.enum(['triggers', 'uses', 'approval'])
|
|
30
|
+
|
|
31
|
+
export const OmTopologyNodeRefSchema = z.discriminatedUnion('kind', [
|
|
32
|
+
z.object({ kind: z.literal('system'), id: ModelIdSchema }),
|
|
33
|
+
z.object({ kind: z.literal('resource'), id: ResourceIdSchema }),
|
|
34
|
+
z.object({ kind: z.literal('ontology'), id: OntologyIdSchema }),
|
|
35
|
+
z.object({ kind: z.literal('policy'), id: ModelIdSchema }),
|
|
36
|
+
z.object({ kind: z.literal('role'), id: ModelIdSchema }),
|
|
37
|
+
z.object({ kind: z.literal('trigger'), id: ResourceIdSchema }),
|
|
38
|
+
z.object({ kind: z.literal('humanCheckpoint'), id: ResourceIdSchema }),
|
|
39
|
+
z.object({ kind: z.literal('externalResource'), id: ResourceIdSchema })
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
export const OmTopologyMetadataSchema = z
|
|
43
|
+
.record(z.string().trim().min(1).max(120), JsonValueSchema)
|
|
44
|
+
.superRefine((metadata, ctx) => {
|
|
45
|
+
function visit(value: unknown, path: Array<string | number>): void {
|
|
46
|
+
if (typeof value === 'string' && SecretLikeMetadataValueSchema.test(value)) {
|
|
47
|
+
ctx.addIssue({
|
|
48
|
+
code: z.ZodIssueCode.custom,
|
|
49
|
+
path,
|
|
50
|
+
message: 'Topology metadata must not contain secret-like values'
|
|
51
|
+
})
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
value.forEach((entry, index) => visit(entry, [...path, index]))
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof value !== 'object' || value === null) return
|
|
61
|
+
|
|
62
|
+
Object.entries(value).forEach(([key, entry]) => {
|
|
63
|
+
if (SecretLikeMetadataKeySchema.test(key)) {
|
|
64
|
+
ctx.addIssue({
|
|
65
|
+
code: z.ZodIssueCode.custom,
|
|
66
|
+
path: [...path, key],
|
|
67
|
+
message: `Topology metadata key "${key}" looks secret-like`
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
visit(entry, [...path, key])
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
visit(metadata, [])
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
export const OmTopologyRelationshipSchema = z.object({
|
|
78
|
+
from: OmTopologyNodeRefSchema,
|
|
79
|
+
kind: OmTopologyRelationshipKindSchema,
|
|
80
|
+
to: OmTopologyNodeRefSchema,
|
|
81
|
+
systemPath: SystemPathSchema.optional(),
|
|
82
|
+
required: z.boolean().optional(),
|
|
83
|
+
metadata: OmTopologyMetadataSchema.optional()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
export const OmTopologyDomainSchema = z
|
|
87
|
+
.object({
|
|
88
|
+
version: z.literal(1).default(1),
|
|
89
|
+
relationships: z.record(z.string().trim().min(1).max(255), OmTopologyRelationshipSchema).default({})
|
|
90
|
+
})
|
|
91
|
+
.default({ version: 1, relationships: {} })
|
|
92
|
+
|
|
93
|
+
export const DEFAULT_ORGANIZATION_MODEL_TOPOLOGY: z.infer<typeof OmTopologyDomainSchema> = {
|
|
94
|
+
version: 1,
|
|
95
|
+
relationships: {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type OmTopologyNodeKind = z.infer<typeof OmTopologyNodeKindSchema>
|
|
99
|
+
export type OmTopologyRelationshipKind = z.infer<typeof OmTopologyRelationshipKindSchema>
|
|
100
|
+
export type OmTopologyNodeRef = z.infer<typeof OmTopologyNodeRefSchema>
|
|
101
|
+
export type OmTopologyMetadata = z.infer<typeof OmTopologyMetadataSchema>
|
|
102
|
+
export type OmTopologyRelationship = z.infer<typeof OmTopologyRelationshipSchema>
|
|
103
|
+
export type OmTopologyDomain = z.infer<typeof OmTopologyDomainSchema>
|
|
104
|
+
|
|
105
|
+
type TypedIdObject = { id: string }
|
|
106
|
+
type TopologyRefFactoryInput = string | TypedIdObject
|
|
107
|
+
type TopologyRelationshipInput = Omit<OmTopologyRelationship, 'from' | 'to'> & {
|
|
108
|
+
from: OmTopologyNodeInput
|
|
109
|
+
to: OmTopologyNodeInput
|
|
110
|
+
}
|
|
111
|
+
type TopologyRelationshipOptions = Omit<Partial<OmTopologyRelationship>, 'from' | 'kind' | 'to'>
|
|
112
|
+
export type OmTopologyNodeInput = OmTopologyNodeRef | ResourceEntry
|
|
113
|
+
|
|
114
|
+
function idFrom(input: TopologyRefFactoryInput): string {
|
|
115
|
+
return typeof input === 'string' ? input : input.id
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parseRef(kind: OmTopologyNodeKind, id: string): OmTopologyNodeRef {
|
|
119
|
+
return OmTopologyNodeRefSchema.parse({ kind, id })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isNodeRef(input: unknown): input is OmTopologyNodeRef {
|
|
123
|
+
return OmTopologyNodeRefSchema.safeParse(input).success
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isResourceEntry(input: unknown): input is ResourceEntry {
|
|
127
|
+
if (typeof input !== 'object' || input === null) return false
|
|
128
|
+
const candidate = input as Partial<ResourceEntry>
|
|
129
|
+
return (
|
|
130
|
+
typeof candidate.id === 'string' &&
|
|
131
|
+
typeof candidate.systemPath === 'string' &&
|
|
132
|
+
typeof candidate.status === 'string' &&
|
|
133
|
+
['workflow', 'agent', 'integration', 'script'].includes(String(candidate.kind))
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const topologyRef = {
|
|
138
|
+
system: (system: TopologyRefFactoryInput) => parseRef('system', idFrom(system)),
|
|
139
|
+
resource: (resource: TopologyRefFactoryInput) => parseRef('resource', idFrom(resource)),
|
|
140
|
+
ontology: (record: TopologyRefFactoryInput) => parseRef('ontology', idFrom(record)),
|
|
141
|
+
policy: (policy: TopologyRefFactoryInput) => parseRef('policy', idFrom(policy)),
|
|
142
|
+
role: (role: TopologyRefFactoryInput) => parseRef('role', idFrom(role)),
|
|
143
|
+
trigger: (trigger: TopologyRefFactoryInput) => parseRef('trigger', idFrom(trigger)),
|
|
144
|
+
humanCheckpoint: (checkpoint: TopologyRefFactoryInput) => parseRef('humanCheckpoint', idFrom(checkpoint)),
|
|
145
|
+
externalResource: (externalResource: TopologyRefFactoryInput) => parseRef('externalResource', idFrom(externalResource))
|
|
146
|
+
} as const
|
|
147
|
+
|
|
148
|
+
export const topologyRelationship = {
|
|
149
|
+
triggers: (from: OmTopologyNodeInput, to: OmTopologyNodeInput, options: TopologyRelationshipOptions = {}) =>
|
|
150
|
+
defineTopologyRelationship({
|
|
151
|
+
...options,
|
|
152
|
+
from,
|
|
153
|
+
kind: 'triggers',
|
|
154
|
+
to
|
|
155
|
+
}),
|
|
156
|
+
uses: (from: OmTopologyNodeInput, to: OmTopologyNodeInput, options: TopologyRelationshipOptions = {}) =>
|
|
157
|
+
defineTopologyRelationship({
|
|
158
|
+
...options,
|
|
159
|
+
from,
|
|
160
|
+
kind: 'uses',
|
|
161
|
+
to
|
|
162
|
+
}),
|
|
163
|
+
approval: (from: OmTopologyNodeInput, to: OmTopologyNodeInput, options: TopologyRelationshipOptions = {}) =>
|
|
164
|
+
defineTopologyRelationship({
|
|
165
|
+
...options,
|
|
166
|
+
from,
|
|
167
|
+
kind: 'approval',
|
|
168
|
+
to
|
|
169
|
+
}),
|
|
170
|
+
usesIntegration: (
|
|
171
|
+
from: OmTopologyNodeInput,
|
|
172
|
+
integration: OmTopologyNodeInput,
|
|
173
|
+
options: TopologyRelationshipOptions = {}
|
|
174
|
+
) =>
|
|
175
|
+
defineTopologyRelationship({
|
|
176
|
+
required: true,
|
|
177
|
+
...options,
|
|
178
|
+
from,
|
|
179
|
+
kind: 'uses',
|
|
180
|
+
to: integration
|
|
181
|
+
}),
|
|
182
|
+
requestsApproval: (
|
|
183
|
+
from: OmTopologyNodeInput,
|
|
184
|
+
checkpoint: TopologyRefFactoryInput,
|
|
185
|
+
options: TopologyRelationshipOptions = {}
|
|
186
|
+
) =>
|
|
187
|
+
defineTopologyRelationship({
|
|
188
|
+
required: true,
|
|
189
|
+
...options,
|
|
190
|
+
from,
|
|
191
|
+
kind: 'approval',
|
|
192
|
+
to: topologyRef.humanCheckpoint(checkpoint)
|
|
193
|
+
}),
|
|
194
|
+
checkpointRoutesTo: (
|
|
195
|
+
checkpoint: TopologyRefFactoryInput,
|
|
196
|
+
to: OmTopologyNodeInput,
|
|
197
|
+
options: TopologyRelationshipOptions = {}
|
|
198
|
+
) =>
|
|
199
|
+
defineTopologyRelationship({
|
|
200
|
+
required: true,
|
|
201
|
+
...options,
|
|
202
|
+
from: topologyRef.humanCheckpoint(checkpoint),
|
|
203
|
+
kind: 'triggers',
|
|
204
|
+
to
|
|
205
|
+
})
|
|
206
|
+
} as const
|
|
207
|
+
|
|
208
|
+
export function compileTopologyNodeRef(input: OmTopologyNodeInput): OmTopologyNodeRef {
|
|
209
|
+
if (isNodeRef(input)) return input
|
|
210
|
+
if (isResourceEntry(input)) return topologyRef.resource(input)
|
|
211
|
+
|
|
212
|
+
throw new Error('Topology node refs must be typed node objects or serializable { kind, id } refs')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function parseTopologyNodeRef(input: string | OmTopologyNodeRef): OmTopologyNodeRef {
|
|
216
|
+
if (typeof input !== 'string') return OmTopologyNodeRefSchema.parse(input)
|
|
217
|
+
|
|
218
|
+
const separatorIndex = input.indexOf(':')
|
|
219
|
+
if (separatorIndex === -1) {
|
|
220
|
+
throw new Error(`Topology node ref "${input}" must use <kind>:<id>`)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const kind = input.slice(0, separatorIndex)
|
|
224
|
+
const id = input.slice(separatorIndex + 1)
|
|
225
|
+
if (!OmTopologyNodeKindSchema.safeParse(kind).success) {
|
|
226
|
+
throw new Error(`Topology node ref "${input}" has unsupported kind "${kind}"`)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return OmTopologyNodeRefSchema.parse({ kind, id })
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function defineTopologyRelationship(input: TopologyRelationshipInput): OmTopologyRelationship {
|
|
233
|
+
return OmTopologyRelationshipSchema.parse({
|
|
234
|
+
...input,
|
|
235
|
+
from: compileTopologyNodeRef(input.from),
|
|
236
|
+
to: compileTopologyNodeRef(input.to)
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function defineTopology(
|
|
241
|
+
relationships: Record<string, TopologyRelationshipInput> | TopologyRelationshipInput[]
|
|
242
|
+
): OmTopologyDomain {
|
|
243
|
+
const entries = Array.isArray(relationships)
|
|
244
|
+
? relationships.map((relationship, index) => [`relationship-${index + 1}`, relationship] as const)
|
|
245
|
+
: Object.entries(relationships)
|
|
246
|
+
|
|
247
|
+
return OmTopologyDomainSchema.parse({
|
|
248
|
+
version: 1,
|
|
249
|
+
relationships: Object.fromEntries(entries.map(([key, relationship]) => [key, defineTopologyRelationship(relationship)]))
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function isOntologyTopologyRef(value: unknown): value is Extract<OmTopologyNodeRef, { kind: 'ontology' }> {
|
|
254
|
+
if (!isNodeRef(value) || value.kind !== 'ontology') return false
|
|
255
|
+
try {
|
|
256
|
+
parseOntologyId(value.id)
|
|
257
|
+
return true
|
|
258
|
+
} catch {
|
|
259
|
+
return false
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -8,10 +8,11 @@ import type {
|
|
|
8
8
|
OrganizationGraphNode,
|
|
9
9
|
OrganizationGraphNodeKind
|
|
10
10
|
} from './types'
|
|
11
|
-
import type { ActionInvocation } from '../domains/actions'
|
|
12
|
-
import type { Entity } from '../domains/entities'
|
|
13
|
-
import type { ResourceEntry } from '../domains/resources'
|
|
14
|
-
import type {
|
|
11
|
+
import type { ActionInvocation } from '../domains/actions'
|
|
12
|
+
import type { Entity } from '../domains/entities'
|
|
13
|
+
import type { ResourceEntry } from '../domains/resources'
|
|
14
|
+
import type { OmTopologyNodeRef } from '../domains/topology'
|
|
15
|
+
import type { EventDescriptor, OrganizationModelSidebarNode } from '../types'
|
|
15
16
|
import {
|
|
16
17
|
getAllPipelines,
|
|
17
18
|
getAllBuildTemplates,
|
|
@@ -286,8 +287,40 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
286
287
|
systemPathByRef.set(path, path)
|
|
287
288
|
systemPathByRef.set(system.id, path)
|
|
288
289
|
}
|
|
289
|
-
const validSystemRefs = new Set(systemPathByRef.keys())
|
|
290
|
-
const systemNodeId = (systemRef: string) => nodeId('system', systemPathByRef.get(systemRef) ?? systemRef)
|
|
290
|
+
const validSystemRefs = new Set(systemPathByRef.keys())
|
|
291
|
+
const systemNodeId = (systemRef: string) => nodeId('system', systemPathByRef.get(systemRef) ?? systemRef)
|
|
292
|
+
|
|
293
|
+
function topologyNodeId(ref: OmTopologyNodeRef): string {
|
|
294
|
+
if (ref.kind === 'system') return systemNodeId(ref.id)
|
|
295
|
+
if (ref.kind === 'resource') return nodeId('resource', ref.id)
|
|
296
|
+
if (ref.kind === 'ontology') return ontologyGraphNodeId(ref.id)
|
|
297
|
+
if (ref.kind === 'policy') return nodeId('policy', ref.id)
|
|
298
|
+
if (ref.kind === 'role') return nodeId('role', ref.id)
|
|
299
|
+
return nodeId('resource', ref.id)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function ensureTopologyNode(ref: OmTopologyNodeRef): string {
|
|
303
|
+
const id = topologyNodeId(ref)
|
|
304
|
+
if (nodeIds.has(id)) return id
|
|
305
|
+
|
|
306
|
+
if (ref.kind === 'resource') {
|
|
307
|
+
ensureResourceNode(nodes, nodeIds, resourceNodesById, ref.id)
|
|
308
|
+
return id
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (ref.kind === 'trigger' || ref.kind === 'humanCheckpoint' || ref.kind === 'externalResource') {
|
|
312
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
313
|
+
id,
|
|
314
|
+
kind: 'resource',
|
|
315
|
+
label: ref.id,
|
|
316
|
+
sourceId: ref.id,
|
|
317
|
+
resourceType:
|
|
318
|
+
ref.kind === 'trigger' ? 'trigger' : ref.kind === 'humanCheckpoint' ? 'human_checkpoint' : 'external'
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return id
|
|
323
|
+
}
|
|
291
324
|
|
|
292
325
|
for (const { path, system } of systemsWithPaths.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
293
326
|
const id = nodeId('system', path)
|
|
@@ -649,7 +682,7 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
649
682
|
})
|
|
650
683
|
}
|
|
651
684
|
|
|
652
|
-
pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, '
|
|
685
|
+
pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'actions', resource.ontology?.actions)
|
|
653
686
|
pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'reads', resource.ontology?.reads)
|
|
654
687
|
pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'writes', resource.ontology?.writes)
|
|
655
688
|
pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'uses_catalog', resource.ontology?.usesCatalogs)
|
|
@@ -698,9 +731,9 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
698
731
|
}
|
|
699
732
|
}
|
|
700
733
|
|
|
701
|
-
for (const policy of Object.values(organizationModel.policies).sort(
|
|
702
|
-
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
703
|
-
)) {
|
|
734
|
+
for (const policy of Object.values(organizationModel.policies).sort(
|
|
735
|
+
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
736
|
+
)) {
|
|
704
737
|
const id = nodeId('policy', policy.id)
|
|
705
738
|
pushUniqueNode(nodes, nodeIds, {
|
|
706
739
|
id,
|
|
@@ -787,11 +820,26 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
787
820
|
targetId: nodeId('role', effect.roleId),
|
|
788
821
|
label: effect.kind
|
|
789
822
|
})
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
for (const
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
for (const [relationshipId, relationship] of Object.entries(organizationModel.topology.relationships).sort(([a], [b]) =>
|
|
828
|
+
a.localeCompare(b)
|
|
829
|
+
)) {
|
|
830
|
+
const sourceId = ensureTopologyNode(relationship.from)
|
|
831
|
+
const targetId = ensureTopologyNode(relationship.to)
|
|
832
|
+
|
|
833
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
834
|
+
id: edgeId(relationship.kind, sourceId, targetId, `topology-${relationshipId}`),
|
|
835
|
+
kind: relationship.kind,
|
|
836
|
+
sourceId,
|
|
837
|
+
targetId,
|
|
838
|
+
relationshipType: relationship.kind
|
|
839
|
+
})
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
for (const segment of Object.values(organizationModel.customers).sort(
|
|
795
843
|
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
796
844
|
)) {
|
|
797
845
|
const id = nodeId('customer-segment', segment.id)
|
|
@@ -33,10 +33,11 @@ export const OrganizationGraphEdgeKindSchema = z.enum([
|
|
|
33
33
|
'affects',
|
|
34
34
|
'emits',
|
|
35
35
|
'originates_from',
|
|
36
|
-
'triggers',
|
|
37
|
-
'
|
|
36
|
+
'triggers',
|
|
37
|
+
'approval',
|
|
38
|
+
'applies_to',
|
|
38
39
|
'effects',
|
|
39
|
-
'
|
|
40
|
+
'actions',
|
|
40
41
|
'reads',
|
|
41
42
|
'writes',
|
|
42
43
|
'uses_catalog'
|
|
@@ -32,11 +32,12 @@ export type OrganizationGraphEdgeKind =
|
|
|
32
32
|
| 'links'
|
|
33
33
|
| 'affects'
|
|
34
34
|
| 'emits'
|
|
35
|
-
| 'originates_from'
|
|
36
|
-
| 'triggers'
|
|
37
|
-
| '
|
|
35
|
+
| 'originates_from'
|
|
36
|
+
| 'triggers'
|
|
37
|
+
| 'approval'
|
|
38
|
+
| 'applies_to'
|
|
38
39
|
| 'effects'
|
|
39
|
-
| '
|
|
40
|
+
| 'actions'
|
|
40
41
|
| 'reads'
|
|
41
42
|
| 'writes'
|
|
42
43
|
| 'uses_catalog'
|
|
@@ -73,9 +73,10 @@ export type {
|
|
|
73
73
|
ActionRegistry
|
|
74
74
|
} from './domains/prospecting'
|
|
75
75
|
export { ProjectsDomainStateSchema } from './domains/projects'
|
|
76
|
-
export * from './domains/systems'
|
|
77
|
-
export * from './domains/resources'
|
|
78
|
-
export
|
|
76
|
+
export * from './domains/systems'
|
|
77
|
+
export * from './domains/resources'
|
|
78
|
+
export * from './domains/topology'
|
|
79
|
+
export {
|
|
79
80
|
DEFAULT_ORGANIZATION_MODEL_NAVIGATION,
|
|
80
81
|
getSortedSidebarEntries,
|
|
81
82
|
NavigationGroupSchema,
|
|
@@ -496,7 +496,6 @@ function addLegacyEntityProjections(
|
|
|
496
496
|
}
|
|
497
497
|
}
|
|
498
498
|
: {}),
|
|
499
|
-
legacyEntityId: entity.id,
|
|
500
499
|
...(entity.rowSchema !== undefined ? { rowSchema: entity.rowSchema } : {}),
|
|
501
500
|
...(entity.stateCatalogId !== undefined ? { stateCatalogId: entity.stateCatalogId } : {})
|
|
502
501
|
}
|
|
@@ -524,8 +523,7 @@ function addLegacyEntityProjections(
|
|
|
524
523
|
from: legacyObjectId(entity),
|
|
525
524
|
to: legacyObjectId(targetEntity),
|
|
526
525
|
cardinality: link.kind,
|
|
527
|
-
...(link.via !== undefined ? { via: link.via } : {})
|
|
528
|
-
legacyEntityId: entity.id
|
|
526
|
+
...(link.via !== undefined ? { via: link.via } : {})
|
|
529
527
|
}
|
|
530
528
|
|
|
531
529
|
addRecord(index, diagnostics, sourcesById, 'linkTypes', linkType, {
|
|
@@ -606,8 +604,7 @@ function addSystemContentProjections(
|
|
|
606
604
|
? { appliesTo: formatOntologyId({ scope: systemPath, kind: 'object', localId: node.data['entityId'] }) }
|
|
607
605
|
: {}),
|
|
608
606
|
...(Object.keys(entries).length > 0 ? { entries } : {}),
|
|
609
|
-
...(node.data !== undefined ? { data: node.data } : {})
|
|
610
|
-
legacyContentId: `${systemPath}:${localId}`
|
|
607
|
+
...(node.data !== undefined ? { data: node.data } : {})
|
|
611
608
|
}
|
|
612
609
|
|
|
613
610
|
addRecord(index, diagnostics, sourcesById, 'catalogTypes', catalogType, {
|
|
@@ -135,17 +135,22 @@ Resource identity is authored in the OM Resources domain. Operations imports des
|
|
|
135
135
|
```ts
|
|
136
136
|
import { defineResources } from '@elevasis/core/organization-model'
|
|
137
137
|
|
|
138
|
-
export const resourceDescriptors = defineResources({
|
|
139
|
-
leadImport: {
|
|
140
|
-
id: 'lgn-01c-apollo-import-workflow',
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
138
|
+
export const resourceDescriptors = defineResources({
|
|
139
|
+
leadImport: {
|
|
140
|
+
id: 'lgn-01c-apollo-import-workflow',
|
|
141
|
+
title: 'Apollo Import',
|
|
142
|
+
description: 'Imports Apollo company records into the lead generation pipeline.',
|
|
143
|
+
kind: 'workflow',
|
|
144
|
+
systemPath: 'sales.lead-gen',
|
|
145
|
+
ownerRoleId: 'role-ops-lead',
|
|
146
|
+
status: 'active',
|
|
147
|
+
ontology: {
|
|
148
|
+
actions: ['sales.lead-gen:action/company.apollo-import'],
|
|
149
|
+
primaryAction: 'sales.lead-gen:action/company.apollo-import'
|
|
150
|
+
},
|
|
151
|
+
codeRefs: [
|
|
152
|
+
{
|
|
153
|
+
path: 'packages/elevasis-operations/src/sales/prospecting/scrape/apollo-import.ts',
|
|
149
154
|
role: 'entrypoint',
|
|
150
155
|
symbol: 'lgnApolloImportWorkflow'
|
|
151
156
|
}
|
|
@@ -107,10 +107,11 @@ export {
|
|
|
107
107
|
AgentKindSchema,
|
|
108
108
|
AgentResourceEntrySchema,
|
|
109
109
|
CodeReferenceRoleSchema,
|
|
110
|
-
CodeReferenceSchema,
|
|
111
|
-
DEFAULT_ORGANIZATION_MODEL_RESOURCES,
|
|
112
|
-
defineResource,
|
|
113
|
-
|
|
110
|
+
CodeReferenceSchema,
|
|
111
|
+
DEFAULT_ORGANIZATION_MODEL_RESOURCES,
|
|
112
|
+
defineResource,
|
|
113
|
+
defineResourceOntology,
|
|
114
|
+
defineResources,
|
|
114
115
|
EventDescriptorSchema,
|
|
115
116
|
EventEmissionDescriptorSchema,
|
|
116
117
|
EventIdSchema,
|
|
@@ -121,11 +122,27 @@ export {
|
|
|
121
122
|
ResourceKindSchema,
|
|
122
123
|
ResourceOntologyBindingSchema,
|
|
123
124
|
ResourcesDomainSchema,
|
|
124
|
-
ScriptResourceEntrySchema,
|
|
125
|
-
ScriptResourceLanguageSchema,
|
|
126
|
-
ScriptResourceSourceSchema,
|
|
127
|
-
WorkflowResourceEntrySchema
|
|
128
|
-
} from './domains/resources'
|
|
125
|
+
ScriptResourceEntrySchema,
|
|
126
|
+
ScriptResourceLanguageSchema,
|
|
127
|
+
ScriptResourceSourceSchema,
|
|
128
|
+
WorkflowResourceEntrySchema
|
|
129
|
+
} from './domains/resources'
|
|
130
|
+
export {
|
|
131
|
+
compileTopologyNodeRef,
|
|
132
|
+
DEFAULT_ORGANIZATION_MODEL_TOPOLOGY,
|
|
133
|
+
defineTopology,
|
|
134
|
+
defineTopologyRelationship,
|
|
135
|
+
isOntologyTopologyRef,
|
|
136
|
+
OmTopologyDomainSchema,
|
|
137
|
+
OmTopologyMetadataSchema,
|
|
138
|
+
OmTopologyNodeKindSchema,
|
|
139
|
+
OmTopologyNodeRefSchema,
|
|
140
|
+
OmTopologyRelationshipKindSchema,
|
|
141
|
+
OmTopologyRelationshipSchema,
|
|
142
|
+
parseTopologyNodeRef,
|
|
143
|
+
topologyRelationship,
|
|
144
|
+
topologyRef
|
|
145
|
+
} from './domains/topology'
|
|
129
146
|
export {
|
|
130
147
|
ActionsDomainSchema,
|
|
131
148
|
ActionIdSchema,
|
|
@@ -249,10 +266,16 @@ export type {
|
|
|
249
266
|
OrganizationModelResourceKind,
|
|
250
267
|
OrganizationModelResourceOntologyBinding,
|
|
251
268
|
OrganizationModelResources,
|
|
252
|
-
OrganizationModelScriptResourceEntry,
|
|
253
|
-
OrganizationModelScriptResourceLanguage,
|
|
254
|
-
OrganizationModelScriptResourceSource,
|
|
255
|
-
|
|
269
|
+
OrganizationModelScriptResourceEntry,
|
|
270
|
+
OrganizationModelScriptResourceLanguage,
|
|
271
|
+
OrganizationModelScriptResourceSource,
|
|
272
|
+
OrganizationModelTopology,
|
|
273
|
+
OrganizationModelTopologyMetadata,
|
|
274
|
+
OrganizationModelTopologyNodeKind,
|
|
275
|
+
OrganizationModelTopologyNodeRef,
|
|
276
|
+
OrganizationModelTopologyRelationship,
|
|
277
|
+
OrganizationModelTopologyRelationshipKind,
|
|
278
|
+
OrganizationModelWorkflowResourceEntry,
|
|
256
279
|
OrganizationModelActions,
|
|
257
280
|
OrganizationModelAction,
|
|
258
281
|
OrganizationModelActionId,
|
|
@@ -8,8 +8,9 @@ import { OfferingsDomainSchema, DEFAULT_ORGANIZATION_MODEL_OFFERINGS } from './d
|
|
|
8
8
|
import { RolesDomainSchema, DEFAULT_ORGANIZATION_MODEL_ROLES } from './domains/roles'
|
|
9
9
|
import { GoalsDomainSchema, DEFAULT_ORGANIZATION_MODEL_GOALS } from './domains/goals'
|
|
10
10
|
import { KnowledgeDomainSchema } from './domains/knowledge'
|
|
11
|
-
import { SystemsDomainSchema, DEFAULT_ORGANIZATION_MODEL_SYSTEMS, type SystemEntry } from './domains/systems'
|
|
12
|
-
import { ResourcesDomainSchema, DEFAULT_ORGANIZATION_MODEL_RESOURCES } from './domains/resources'
|
|
11
|
+
import { SystemsDomainSchema, DEFAULT_ORGANIZATION_MODEL_SYSTEMS, type SystemEntry } from './domains/systems'
|
|
12
|
+
import { ResourcesDomainSchema, DEFAULT_ORGANIZATION_MODEL_RESOURCES } from './domains/resources'
|
|
13
|
+
import { OmTopologyDomainSchema, DEFAULT_ORGANIZATION_MODEL_TOPOLOGY, type OmTopologyNodeRef } from './domains/topology'
|
|
13
14
|
import { ActionsDomainSchema, DEFAULT_ORGANIZATION_MODEL_ACTIONS } from './domains/actions'
|
|
14
15
|
import { EntitiesDomainSchema, DEFAULT_ORGANIZATION_MODEL_ENTITIES } from './domains/entities'
|
|
15
16
|
import { PoliciesDomainSchema, DEFAULT_ORGANIZATION_MODEL_POLICIES } from './domains/policies'
|
|
@@ -33,7 +34,8 @@ export const OrganizationModelDomainKeySchema = z.enum([
|
|
|
33
34
|
'systems',
|
|
34
35
|
'ontology',
|
|
35
36
|
'resources',
|
|
36
|
-
'
|
|
37
|
+
'topology',
|
|
38
|
+
'actions',
|
|
37
39
|
'entities',
|
|
38
40
|
'policies',
|
|
39
41
|
'knowledge'
|
|
@@ -57,7 +59,8 @@ export const DEFAULT_ORGANIZATION_MODEL_DOMAIN_METADATA: Record<
|
|
|
57
59
|
systems: { version: 1, lastModified: '2026-05-10' },
|
|
58
60
|
ontology: { version: 1, lastModified: '2026-05-14' },
|
|
59
61
|
resources: { version: 1, lastModified: '2026-05-10' },
|
|
60
|
-
|
|
62
|
+
topology: { version: 1, lastModified: '2026-05-14' },
|
|
63
|
+
actions: { version: 1, lastModified: '2026-05-10' },
|
|
61
64
|
entities: { version: 1, lastModified: '2026-05-10' },
|
|
62
65
|
policies: { version: 1, lastModified: '2026-05-10' },
|
|
63
66
|
knowledge: { version: 1, lastModified: '2026-05-10' }
|
|
@@ -74,7 +77,8 @@ export const OrganizationModelDomainMetadataByDomainSchema = z
|
|
|
74
77
|
systems: OrganizationModelDomainMetadataSchema,
|
|
75
78
|
ontology: OrganizationModelDomainMetadataSchema,
|
|
76
79
|
resources: OrganizationModelDomainMetadataSchema,
|
|
77
|
-
|
|
80
|
+
topology: OrganizationModelDomainMetadataSchema,
|
|
81
|
+
actions: OrganizationModelDomainMetadataSchema,
|
|
78
82
|
entities: OrganizationModelDomainMetadataSchema,
|
|
79
83
|
policies: OrganizationModelDomainMetadataSchema,
|
|
80
84
|
knowledge: OrganizationModelDomainMetadataSchema
|
|
@@ -104,7 +108,8 @@ const OrganizationModelSchemaBase = z.object({
|
|
|
104
108
|
systems: SystemsDomainSchema.default(DEFAULT_ORGANIZATION_MODEL_SYSTEMS),
|
|
105
109
|
ontology: OntologyScopeSchema.default(DEFAULT_ONTOLOGY_SCOPE),
|
|
106
110
|
resources: ResourcesDomainSchema.default(DEFAULT_ORGANIZATION_MODEL_RESOURCES),
|
|
107
|
-
|
|
111
|
+
topology: OmTopologyDomainSchema.default(DEFAULT_ORGANIZATION_MODEL_TOPOLOGY),
|
|
112
|
+
actions: ActionsDomainSchema.default(DEFAULT_ORGANIZATION_MODEL_ACTIONS),
|
|
108
113
|
entities: EntitiesDomainSchema.default(DEFAULT_ORGANIZATION_MODEL_ENTITIES),
|
|
109
114
|
policies: PoliciesDomainSchema.default(DEFAULT_ORGANIZATION_MODEL_POLICIES),
|
|
110
115
|
// D3: flat Record<id, OrgKnowledgeNode> — no wrapper object
|
|
@@ -526,6 +531,32 @@ export const OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((
|
|
|
526
531
|
} satisfies Record<OntologyKind, Record<string, unknown>>
|
|
527
532
|
const ontologyIds = new Set(Object.values(ontologyIndexByKind).flatMap((index) => Object.keys(index)))
|
|
528
533
|
|
|
534
|
+
function topologyTargetExists(ref: OmTopologyNodeRef): boolean {
|
|
535
|
+
if (ref.kind === 'system') return systemsById.has(ref.id)
|
|
536
|
+
if (ref.kind === 'resource') return resourcesById.has(ref.id)
|
|
537
|
+
if (ref.kind === 'ontology') return ontologyIds.has(ref.id)
|
|
538
|
+
if (ref.kind === 'policy') return policiesById.has(ref.id)
|
|
539
|
+
if (ref.kind === 'role') return rolesById.has(ref.id)
|
|
540
|
+
|
|
541
|
+
// Trigger, human checkpoint, and external resource refs are projected
|
|
542
|
+
// topology nodes during the bridge period; their owning runtime indexes are
|
|
543
|
+
// validated by deployment projection in later waves.
|
|
544
|
+
return true
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
Object.entries(model.topology.relationships).forEach(([relationshipId, relationship]) => {
|
|
548
|
+
;(['from', 'to'] as const).forEach((side) => {
|
|
549
|
+
const ref = relationship[side]
|
|
550
|
+
if (topologyTargetExists(ref)) return
|
|
551
|
+
|
|
552
|
+
addIssue(
|
|
553
|
+
ctx,
|
|
554
|
+
['topology', 'relationships', relationshipId, side],
|
|
555
|
+
`Topology relationship "${relationshipId}" ${side} references unknown ${ref.kind} "${ref.id}"`
|
|
556
|
+
)
|
|
557
|
+
})
|
|
558
|
+
})
|
|
559
|
+
|
|
529
560
|
const ontologyReferenceKeyKinds = {
|
|
530
561
|
valueType: 'value-type',
|
|
531
562
|
catalogType: 'catalog',
|
|
@@ -694,15 +725,23 @@ export const OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((
|
|
|
694
725
|
|
|
695
726
|
function validateResourceOntologyBinding(
|
|
696
727
|
resourceId: string,
|
|
697
|
-
bindingKey: '
|
|
728
|
+
bindingKey: 'actions' | 'primaryAction' | 'reads' | 'writes' | 'usesCatalogs' | 'emits',
|
|
698
729
|
expectedKind: OntologyKind,
|
|
699
|
-
ids: string[] | undefined
|
|
730
|
+
ids: string[] | string | undefined
|
|
700
731
|
): void {
|
|
701
|
-
ids
|
|
732
|
+
const ontologyIds = ids === undefined ? [] : Array.isArray(ids) ? ids : [ids]
|
|
733
|
+
|
|
734
|
+
ontologyIds.forEach((ontologyId, ontologyIndex) => {
|
|
702
735
|
if (ontologyIndexByKind[expectedKind][ontologyId] === undefined) {
|
|
703
736
|
addIssue(
|
|
704
737
|
ctx,
|
|
705
|
-
[
|
|
738
|
+
[
|
|
739
|
+
'resources',
|
|
740
|
+
resourceId,
|
|
741
|
+
'ontology',
|
|
742
|
+
bindingKey,
|
|
743
|
+
...(Array.isArray(ids) ? [ontologyIndex] : [])
|
|
744
|
+
],
|
|
706
745
|
`Resource "${resourceId}" ontology binding "${bindingKey}" references unknown ${expectedKind} ontology ID "${ontologyId}"`
|
|
707
746
|
)
|
|
708
747
|
}
|
|
@@ -713,7 +752,8 @@ export const OrganizationModelSchema = OrganizationModelSchemaBase.superRefine((
|
|
|
713
752
|
const binding = resource.ontology
|
|
714
753
|
if (binding === undefined) return
|
|
715
754
|
|
|
716
|
-
validateResourceOntologyBinding(resource.id, '
|
|
755
|
+
validateResourceOntologyBinding(resource.id, 'actions', 'action', binding.actions)
|
|
756
|
+
validateResourceOntologyBinding(resource.id, 'primaryAction', 'action', binding.primaryAction)
|
|
717
757
|
validateResourceOntologyBinding(resource.id, 'reads', 'object', binding.reads)
|
|
718
758
|
validateResourceOntologyBinding(resource.id, 'writes', 'object', binding.writes)
|
|
719
759
|
validateResourceOntologyBinding(resource.id, 'usesCatalogs', 'catalog', binding.usesCatalogs)
|