@elevasis/core 0.22.0 → 0.23.0
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 +2330 -2391
- package/dist/index.js +2322 -1147
- package/dist/knowledge/index.d.ts +702 -1136
- package/dist/knowledge/index.js +9 -9
- package/dist/organization-model/index.d.ts +2330 -2391
- package/dist/organization-model/index.js +2322 -1147
- package/dist/test-utils/index.d.ts +703 -1106
- package/dist/test-utils/index.js +1735 -1089
- package/package.json +1 -1
- package/src/__tests__/template-core-compatibility.test.ts +11 -79
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +360 -98
- package/src/business/acquisition/api-schemas.test.ts +2 -2
- package/src/business/acquisition/api-schemas.ts +7 -9
- package/src/business/acquisition/build-templates.test.ts +4 -4
- package/src/business/acquisition/build-templates.ts +72 -30
- package/src/business/acquisition/crm-state-actions.test.ts +13 -11
- package/src/business/acquisition/types.ts +7 -3
- package/src/execution/engine/agent/core/types.ts +1 -1
- package/src/execution/engine/workflow/types.ts +2 -2
- package/src/knowledge/README.md +8 -7
- package/src/knowledge/__tests__/queries.test.ts +74 -73
- package/src/knowledge/format.ts +10 -9
- package/src/knowledge/index.ts +1 -1
- package/src/knowledge/published.ts +1 -1
- package/src/knowledge/queries.ts +26 -25
- package/src/organization-model/README.md +66 -26
- package/src/organization-model/__tests__/content-kinds-registry.test.ts +210 -0
- package/src/organization-model/__tests__/defaults.test.ts +72 -98
- package/src/organization-model/__tests__/domains/actions.test.ts +56 -0
- package/src/organization-model/__tests__/domains/customers.test.ts +299 -295
- package/src/organization-model/__tests__/domains/entities.test.ts +56 -0
- package/src/organization-model/__tests__/domains/goals.test.ts +493 -479
- package/src/organization-model/__tests__/domains/identity.test.ts +280 -279
- package/src/organization-model/__tests__/domains/navigation.test.ts +268 -212
- package/src/organization-model/__tests__/domains/offerings.test.ts +414 -419
- package/src/organization-model/__tests__/domains/policies.test.ts +323 -0
- package/src/organization-model/__tests__/domains/resource-mappings.test.ts +271 -271
- package/src/organization-model/__tests__/domains/resources.test.ts +159 -37
- package/src/organization-model/__tests__/domains/roles.test.ts +147 -86
- package/src/organization-model/__tests__/domains/statuses.test.ts +246 -243
- package/src/organization-model/__tests__/domains/systems.test.ts +67 -51
- package/src/organization-model/__tests__/flatten-additive-merge.test.ts +361 -0
- package/src/organization-model/__tests__/foundation.test.ts +74 -102
- package/src/organization-model/__tests__/get-resources-for-system.test.ts +144 -0
- package/src/organization-model/__tests__/graph.test.ts +899 -71
- package/src/organization-model/__tests__/knowledge.test.ts +173 -52
- package/src/organization-model/__tests__/lookup-helpers.test.ts +438 -0
- package/src/organization-model/__tests__/migration-helpers.test.ts +591 -0
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +36 -27
- package/src/organization-model/__tests__/recursive-system-schema.test.ts +520 -0
- package/src/organization-model/__tests__/resolve.test.ts +174 -23
- package/src/organization-model/__tests__/schema.test.ts +291 -114
- package/src/organization-model/__tests__/surface-projection.test.ts +207 -97
- package/src/organization-model/catalogs/lead-gen.ts +144 -0
- package/src/organization-model/content-kinds/config.ts +36 -0
- package/src/organization-model/content-kinds/index.ts +74 -0
- package/src/organization-model/content-kinds/pipeline.ts +68 -0
- package/src/organization-model/content-kinds/registry.ts +44 -0
- package/src/organization-model/content-kinds/status.ts +71 -0
- package/src/organization-model/content-kinds/template.ts +83 -0
- package/src/organization-model/content-kinds/types.ts +117 -0
- package/src/organization-model/contracts.ts +13 -3
- package/src/organization-model/defaults.ts +488 -96
- package/src/organization-model/domains/actions.ts +239 -0
- package/src/organization-model/domains/customers.ts +78 -75
- package/src/organization-model/domains/entities.ts +144 -0
- package/src/organization-model/domains/goals.ts +83 -80
- package/src/organization-model/domains/knowledge.ts +74 -16
- package/src/organization-model/domains/navigation.ts +107 -384
- package/src/organization-model/domains/offerings.ts +71 -66
- package/src/organization-model/domains/policies.ts +102 -0
- package/src/organization-model/domains/projects.ts +14 -48
- package/src/organization-model/domains/prospecting.ts +62 -181
- package/src/organization-model/domains/resources.ts +81 -24
- package/src/organization-model/domains/roles.ts +13 -10
- package/src/organization-model/domains/sales.ts +10 -219
- package/src/organization-model/domains/shared.ts +57 -57
- package/src/organization-model/domains/statuses.ts +339 -130
- package/src/organization-model/domains/systems.ts +186 -29
- package/src/organization-model/foundation.ts +54 -67
- package/src/organization-model/graph/build.ts +682 -54
- package/src/organization-model/graph/link.ts +1 -1
- package/src/organization-model/graph/schema.ts +24 -9
- package/src/organization-model/graph/types.ts +20 -7
- package/src/organization-model/helpers.ts +231 -26
- package/src/organization-model/index.ts +116 -5
- package/src/organization-model/migration-helpers.ts +249 -0
- package/src/organization-model/organization-graph.mdx +16 -15
- package/src/organization-model/organization-model.mdx +89 -41
- package/src/organization-model/published.ts +120 -18
- package/src/organization-model/resolve.ts +117 -54
- package/src/organization-model/schema.ts +561 -140
- package/src/organization-model/surface-projection.ts +116 -122
- package/src/organization-model/types.ts +102 -21
- package/src/platform/constants/versions.ts +1 -1
- package/src/platform/registry/__tests__/command-view.test.ts +6 -8
- package/src/platform/registry/__tests__/resource-link.test.ts +13 -8
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +16 -31
- package/src/platform/registry/__tests__/resource-registry.nested-systems.test.ts +245 -0
- package/src/platform/registry/__tests__/resource-registry.test.ts +9 -7
- package/src/platform/registry/__tests__/validation.test.ts +15 -11
- package/src/platform/registry/resource-registry.ts +20 -8
- package/src/platform/registry/serialization.ts +7 -7
- package/src/platform/registry/types.ts +3 -3
- package/src/platform/registry/validation.ts +17 -15
- package/src/reference/_generated/contracts.md +362 -99
- package/src/reference/glossary.md +18 -18
- package/src/supabase/database.types.ts +60 -0
- package/src/test-utils/test-utils.test.ts +1 -6
- package/src/organization-model/__tests__/domains/operations.test.ts +0 -203
- package/src/organization-model/domains/features.ts +0 -31
- package/src/organization-model/domains/operations.ts +0 -85
|
@@ -7,7 +7,20 @@ import type {
|
|
|
7
7
|
OrganizationGraphNode,
|
|
8
8
|
OrganizationGraphNodeKind
|
|
9
9
|
} from './types'
|
|
10
|
-
import {
|
|
10
|
+
import type { ActionInvocation } from '../domains/actions'
|
|
11
|
+
import type { Entity } from '../domains/entities'
|
|
12
|
+
import type { ResourceEntry } from '../domains/resources'
|
|
13
|
+
import type { EventDescriptor, OrganizationModelSidebarNode } from '../types'
|
|
14
|
+
import {
|
|
15
|
+
getAllPipelines,
|
|
16
|
+
getAllBuildTemplates,
|
|
17
|
+
getAllProjectStatuses,
|
|
18
|
+
getAllProspectingStages
|
|
19
|
+
} from '../migration-helpers'
|
|
20
|
+
import { LEAD_GEN_STAGE_CATALOG } from '../catalogs/lead-gen'
|
|
21
|
+
import { listAllSystems } from '../helpers'
|
|
22
|
+
|
|
23
|
+
type EventEmissionDescriptor = NonNullable<Extract<ResourceEntry, { kind: 'workflow' }>['emits']>[number]
|
|
11
24
|
|
|
12
25
|
function nodeId(kind: OrganizationGraphNodeKind, sourceId?: string): string {
|
|
13
26
|
return kind === 'organization' ? 'organization-model' : `${kind}:${sourceId ?? ''}`
|
|
@@ -29,6 +42,36 @@ function pushUniqueEdge(edges: OrganizationGraphEdge[], seen: Set<string>, edge:
|
|
|
29
42
|
edges.push(edge)
|
|
30
43
|
}
|
|
31
44
|
|
|
45
|
+
function collectSidebarGraphNodes(
|
|
46
|
+
nodes: Record<string, OrganizationModelSidebarNode>,
|
|
47
|
+
groups: Array<{
|
|
48
|
+
id: string
|
|
49
|
+
node: Extract<OrganizationModelSidebarNode, { type: 'group' }>
|
|
50
|
+
parentGroupId?: string
|
|
51
|
+
}>,
|
|
52
|
+
surfaces: Array<{
|
|
53
|
+
id: string
|
|
54
|
+
node: Extract<OrganizationModelSidebarNode, { type: 'surface' }>
|
|
55
|
+
parentGroupId?: string
|
|
56
|
+
}>,
|
|
57
|
+
parentGroupId?: string
|
|
58
|
+
): void {
|
|
59
|
+
Object.entries(nodes)
|
|
60
|
+
.sort(([leftId, left], [rightId, right]) => {
|
|
61
|
+
const orderDelta = (left.order ?? Number.MAX_SAFE_INTEGER) - (right.order ?? Number.MAX_SAFE_INTEGER)
|
|
62
|
+
return orderDelta === 0 ? leftId.localeCompare(rightId) : orderDelta
|
|
63
|
+
})
|
|
64
|
+
.forEach(([id, node]) => {
|
|
65
|
+
if (node.type === 'group') {
|
|
66
|
+
groups.push({ id, node, ...(parentGroupId !== undefined ? { parentGroupId } : {}) })
|
|
67
|
+
collectSidebarGraphNodes(node.children, groups, surfaces, id)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
surfaces.push({ id, node, ...(parentGroupId !== undefined ? { parentGroupId } : {}) })
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
32
75
|
function upsertResourceNode(
|
|
33
76
|
nodes: OrganizationGraphNode[],
|
|
34
77
|
seen: Set<string>,
|
|
@@ -88,6 +131,12 @@ function normalizeCommandViewResourceType(
|
|
|
88
131
|
return resourceType === 'human' ? 'human_checkpoint' : resourceType
|
|
89
132
|
}
|
|
90
133
|
|
|
134
|
+
function normalizeOrganizationModelResourceType(
|
|
135
|
+
resourceType: ResourceEntry['kind']
|
|
136
|
+
): OrganizationGraphNode['resourceType'] {
|
|
137
|
+
return resourceType
|
|
138
|
+
}
|
|
139
|
+
|
|
91
140
|
function collectCommandViewResources(commandViewData: CommandViewData): CommandViewResource[] {
|
|
92
141
|
return [
|
|
93
142
|
...commandViewData.workflows,
|
|
@@ -99,20 +148,81 @@ function collectCommandViewResources(commandViewData: CommandViewData): CommandV
|
|
|
99
148
|
]
|
|
100
149
|
}
|
|
101
150
|
|
|
102
|
-
function
|
|
151
|
+
function invocationSignature(invocation: ActionInvocation): string {
|
|
152
|
+
switch (invocation.kind) {
|
|
153
|
+
case 'slash-command':
|
|
154
|
+
return `${invocation.kind}:${invocation.command}`
|
|
155
|
+
case 'mcp-tool':
|
|
156
|
+
return `${invocation.kind}:${invocation.server}:${invocation.name}`
|
|
157
|
+
case 'api-endpoint':
|
|
158
|
+
return `${invocation.kind}:${invocation.method}:${invocation.path}`
|
|
159
|
+
case 'script-execution':
|
|
160
|
+
return `${invocation.kind}:${invocation.resourceId}`
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function invocationLabel(invocation: ActionInvocation): string {
|
|
165
|
+
switch (invocation.kind) {
|
|
166
|
+
case 'slash-command':
|
|
167
|
+
return invocation.command
|
|
168
|
+
case 'mcp-tool':
|
|
169
|
+
return `${invocation.server}.${invocation.name}`
|
|
170
|
+
case 'api-endpoint':
|
|
171
|
+
return `${invocation.method} ${invocation.path}`
|
|
172
|
+
case 'script-execution':
|
|
173
|
+
return `script ${invocation.resourceId}`
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function eventNodeId(eventId: string): string {
|
|
178
|
+
return nodeId('event', eventId)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function buildResourceEventDescriptor(resourceId: string, emission: EventEmissionDescriptor): EventDescriptor {
|
|
182
|
+
return {
|
|
183
|
+
...emission,
|
|
184
|
+
id: `${resourceId}:${emission.eventKey}`,
|
|
185
|
+
ownerId: resourceId,
|
|
186
|
+
ownerKind: 'resource'
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildEntityEventDescriptor(entity: Entity, eventKey: string, label: string): EventDescriptor {
|
|
191
|
+
return {
|
|
192
|
+
id: `${entity.id}:${eventKey}`,
|
|
193
|
+
ownerId: entity.id,
|
|
194
|
+
ownerKind: 'entity',
|
|
195
|
+
eventKey,
|
|
196
|
+
label
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function pushEventProjection(
|
|
201
|
+
nodes: OrganizationGraphNode[],
|
|
202
|
+
nodeIds: Set<string>,
|
|
103
203
|
edges: OrganizationGraphEdge[],
|
|
104
204
|
edgeIds: Set<string>,
|
|
105
|
-
|
|
106
|
-
|
|
205
|
+
eventNodeIdsByEventId: Map<string, string>,
|
|
206
|
+
event: EventDescriptor,
|
|
207
|
+
sourceNodeId: string,
|
|
208
|
+
edgeKind: 'emits' | 'originates_from'
|
|
107
209
|
): void {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
210
|
+
const id = eventNodeId(event.id)
|
|
211
|
+
eventNodeIdsByEventId.set(event.id, id)
|
|
212
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
213
|
+
id,
|
|
214
|
+
kind: 'event',
|
|
215
|
+
label: event.label,
|
|
216
|
+
sourceId: event.id
|
|
217
|
+
})
|
|
218
|
+
const sourceId = edgeKind === 'originates_from' ? id : sourceNodeId
|
|
219
|
+
const targetId = edgeKind === 'originates_from' ? sourceNodeId : id
|
|
220
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
221
|
+
id: edgeId(edgeKind, sourceId, targetId),
|
|
222
|
+
kind: edgeKind,
|
|
223
|
+
sourceId,
|
|
224
|
+
targetId
|
|
225
|
+
})
|
|
116
226
|
}
|
|
117
227
|
|
|
118
228
|
export function buildOrganizationGraph(input: BuildOrganizationGraphInput): OrganizationGraph {
|
|
@@ -125,6 +235,9 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
125
235
|
const nodeIds = new Set<string>()
|
|
126
236
|
const edgeIds = new Set<string>()
|
|
127
237
|
const resourceNodesById = new Map<string, OrganizationGraphNode>()
|
|
238
|
+
const organizationModelResourceIds = new Set(Object.keys(organizationModel.resources))
|
|
239
|
+
const actionIdsByInvocation = new Map<string, string[]>()
|
|
240
|
+
const projectedEventNodeIdsByEventId = new Map<string, string>()
|
|
128
241
|
|
|
129
242
|
const organizationNode: OrganizationGraphNode = {
|
|
130
243
|
id: nodeId('organization'),
|
|
@@ -132,18 +245,25 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
132
245
|
label: 'Organization Model'
|
|
133
246
|
}
|
|
134
247
|
pushUniqueNode(nodes, nodeIds, organizationNode)
|
|
248
|
+
const systemsWithPaths = listAllSystems(organizationModel)
|
|
249
|
+
const systemPathByRef = new Map<string, string>()
|
|
250
|
+
for (const { path, system } of systemsWithPaths) {
|
|
251
|
+
systemPathByRef.set(path, path)
|
|
252
|
+
systemPathByRef.set(system.id, path)
|
|
253
|
+
}
|
|
254
|
+
const validSystemRefs = new Set(systemPathByRef.keys())
|
|
255
|
+
const systemNodeId = (systemRef: string) => nodeId('system', systemPathByRef.get(systemRef) ?? systemRef)
|
|
135
256
|
|
|
136
|
-
for (const
|
|
137
|
-
const id = nodeId('
|
|
257
|
+
for (const { path, system } of systemsWithPaths.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
258
|
+
const id = nodeId('system', path)
|
|
138
259
|
pushUniqueNode(nodes, nodeIds, {
|
|
139
260
|
id,
|
|
140
|
-
kind: '
|
|
141
|
-
label:
|
|
142
|
-
sourceId:
|
|
143
|
-
description:
|
|
144
|
-
icon:
|
|
145
|
-
enabled:
|
|
146
|
-
featureId: feature.id
|
|
261
|
+
kind: 'system',
|
|
262
|
+
label: system.label ?? system.title ?? system.id,
|
|
263
|
+
sourceId: path,
|
|
264
|
+
description: system.description,
|
|
265
|
+
icon: system.ui?.icon ?? system.icon,
|
|
266
|
+
enabled: system.enabled
|
|
147
267
|
})
|
|
148
268
|
pushUniqueEdge(edges, edgeIds, {
|
|
149
269
|
id: edgeId('contains', organizationNode.id, id),
|
|
@@ -151,18 +271,54 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
151
271
|
sourceId: organizationNode.id,
|
|
152
272
|
targetId: id
|
|
153
273
|
})
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
274
|
+
const parentSystemId = path.includes('.') ? path.slice(0, path.lastIndexOf('.')) : undefined
|
|
275
|
+
if (parentSystemId !== undefined) {
|
|
156
276
|
pushUniqueEdge(edges, edgeIds, {
|
|
157
|
-
id: edgeId('contains', nodeId('
|
|
277
|
+
id: edgeId('contains', nodeId('system', parentSystemId), id),
|
|
158
278
|
kind: 'contains',
|
|
159
|
-
sourceId: nodeId('
|
|
279
|
+
sourceId: nodeId('system', parentSystemId),
|
|
160
280
|
targetId: id
|
|
161
281
|
})
|
|
162
282
|
}
|
|
163
283
|
}
|
|
164
284
|
|
|
165
|
-
for (const
|
|
285
|
+
for (const role of Object.values(organizationModel.roles).sort((a, b) => a.id.localeCompare(b.id))) {
|
|
286
|
+
const id = nodeId('role', role.id)
|
|
287
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
288
|
+
id,
|
|
289
|
+
kind: 'role',
|
|
290
|
+
label: role.title,
|
|
291
|
+
sourceId: role.id,
|
|
292
|
+
description: role.responsibilities.length > 0 ? role.responsibilities.join('; ') : undefined
|
|
293
|
+
})
|
|
294
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
295
|
+
id: edgeId('contains', organizationNode.id, id),
|
|
296
|
+
kind: 'contains',
|
|
297
|
+
sourceId: organizationNode.id,
|
|
298
|
+
targetId: id
|
|
299
|
+
})
|
|
300
|
+
if (role.reportsToId !== undefined) {
|
|
301
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
302
|
+
id: edgeId('references', id, nodeId('role', role.reportsToId), 'reports-to'),
|
|
303
|
+
kind: 'references',
|
|
304
|
+
sourceId: id,
|
|
305
|
+
targetId: nodeId('role', role.reportsToId),
|
|
306
|
+
label: 'reports to'
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
for (const systemId of role.responsibleFor ?? []) {
|
|
310
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
311
|
+
id: edgeId('governs', id, systemNodeId(systemId), 'responsible-for'),
|
|
312
|
+
kind: 'governs',
|
|
313
|
+
sourceId: id,
|
|
314
|
+
targetId: systemNodeId(systemId),
|
|
315
|
+
label: 'responsible for'
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Phase 4: knowledge is now Record<id, OrgKnowledgeNode> (D3); iterate Object.values.
|
|
321
|
+
for (const node of Object.values(organizationModel.knowledge).sort((a, b) => a.id.localeCompare(b.id))) {
|
|
166
322
|
const id = nodeId('knowledge', node.id)
|
|
167
323
|
pushUniqueNode(nodes, nodeIds, {
|
|
168
324
|
id,
|
|
@@ -179,18 +335,20 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
179
335
|
targetId: id
|
|
180
336
|
})
|
|
181
337
|
for (const link of node.links) {
|
|
338
|
+
const targetId = link.target.kind === 'system' ? systemNodeId(link.target.id) : link.nodeId
|
|
182
339
|
pushUniqueEdge(edges, edgeIds, {
|
|
183
|
-
id: edgeId('governs', id,
|
|
340
|
+
id: edgeId('governs', id, targetId),
|
|
184
341
|
kind: 'governs',
|
|
185
342
|
sourceId: id,
|
|
186
|
-
targetId
|
|
343
|
+
targetId
|
|
187
344
|
})
|
|
188
345
|
}
|
|
189
346
|
}
|
|
190
347
|
|
|
348
|
+
// Phase 4: prospecting domain removed; read stages via migration helper.
|
|
191
349
|
const allStages = [
|
|
192
|
-
...organizationModel
|
|
193
|
-
...organizationModel
|
|
350
|
+
...getAllProspectingStages(organizationModel, 'company'),
|
|
351
|
+
...getAllProspectingStages(organizationModel, 'contact')
|
|
194
352
|
].sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))
|
|
195
353
|
|
|
196
354
|
for (const stage of allStages) {
|
|
@@ -211,14 +369,314 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
211
369
|
})
|
|
212
370
|
}
|
|
213
371
|
|
|
214
|
-
for (const
|
|
215
|
-
const id = nodeId('
|
|
372
|
+
for (const action of Object.values(organizationModel.actions).sort((a, b) => a.id.localeCompare(b.id))) {
|
|
373
|
+
const id = nodeId('action', action.id)
|
|
374
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
375
|
+
id,
|
|
376
|
+
kind: 'action',
|
|
377
|
+
label: action.label,
|
|
378
|
+
sourceId: action.id,
|
|
379
|
+
description: action.description
|
|
380
|
+
})
|
|
381
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
382
|
+
id: edgeId('contains', organizationNode.id, id),
|
|
383
|
+
kind: 'contains',
|
|
384
|
+
sourceId: organizationNode.id,
|
|
385
|
+
targetId: id
|
|
386
|
+
})
|
|
387
|
+
if (action.resourceId !== undefined) {
|
|
388
|
+
const resourceNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, action.resourceId)
|
|
389
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
390
|
+
id: edgeId('maps_to', id, resourceNode.id),
|
|
391
|
+
kind: 'maps_to',
|
|
392
|
+
sourceId: id,
|
|
393
|
+
targetId: resourceNode.id
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
for (const entityId of action.affects ?? []) {
|
|
398
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
399
|
+
id: edgeId('affects', id, nodeId('entity', entityId)),
|
|
400
|
+
kind: 'affects',
|
|
401
|
+
sourceId: id,
|
|
402
|
+
targetId: nodeId('entity', entityId)
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
for (const invocation of action.invocations) {
|
|
407
|
+
const key = invocationSignature(invocation)
|
|
408
|
+
const existing = actionIdsByInvocation.get(key)
|
|
409
|
+
if (existing) {
|
|
410
|
+
existing.push(id)
|
|
411
|
+
} else {
|
|
412
|
+
actionIdsByInvocation.set(key, [id])
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
for (const entity of Object.values(organizationModel.entities).sort(
|
|
418
|
+
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
419
|
+
)) {
|
|
420
|
+
const id = nodeId('entity', entity.id)
|
|
421
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
422
|
+
id,
|
|
423
|
+
kind: 'entity',
|
|
424
|
+
label: entity.label,
|
|
425
|
+
sourceId: entity.id,
|
|
426
|
+
description: entity.description
|
|
427
|
+
})
|
|
428
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
429
|
+
id: edgeId('contains', systemNodeId(entity.ownedBySystemId), id, 'system-entity'),
|
|
430
|
+
kind: 'contains',
|
|
431
|
+
sourceId: systemNodeId(entity.ownedBySystemId),
|
|
432
|
+
targetId: id
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
for (const [linkIndex, link] of (entity.links ?? []).entries()) {
|
|
436
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
437
|
+
id: edgeId('links', id, nodeId('entity', link.toEntity), `${link.kind}-${linkIndex}`),
|
|
438
|
+
kind: 'links',
|
|
439
|
+
sourceId: id,
|
|
440
|
+
targetId: nodeId('entity', link.toEntity),
|
|
441
|
+
label: link.label ?? link.kind
|
|
442
|
+
})
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (entity.stateCatalogId !== undefined) {
|
|
446
|
+
const stateEvents: EventDescriptor[] = []
|
|
447
|
+
|
|
448
|
+
// Phase 4: model.statuses removed (D1). Status data lives in system.content.
|
|
449
|
+
// Graph projection of statuses via content nodes is Wave 5 work.
|
|
450
|
+
// For now, emit no status-derived state events from the deleted domain field.
|
|
451
|
+
|
|
452
|
+
if (entity.stateCatalogId === 'crm.pipeline') {
|
|
453
|
+
// Phase 4: model.sales removed (D8). Read pipelines via migration helper.
|
|
454
|
+
for (const { pipeline } of getAllPipelines(organizationModel)
|
|
455
|
+
.filter(({ pipeline: p }) => p.entityId === entity.id)
|
|
456
|
+
.sort((a, b) => a.pipeline.id.localeCompare(b.pipeline.id))) {
|
|
457
|
+
for (const stage of [...pipeline.stages].sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))) {
|
|
458
|
+
stateEvents.push(buildEntityEventDescriptor(entity, stage.id, stage.label))
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (entity.stateCatalogId === 'delivery.task') {
|
|
464
|
+
// Phase 4: model.projects removed (D8). Read statuses via migration helper.
|
|
465
|
+
for (const status of getAllProjectStatuses(organizationModel, 'task').sort(
|
|
466
|
+
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
467
|
+
)) {
|
|
468
|
+
stateEvents.push(buildEntityEventDescriptor(entity, status.id, status.label))
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (entity.stateCatalogId === 'lead-gen.company' || entity.stateCatalogId === 'lead-gen.contact') {
|
|
473
|
+
const leadGenEntity = entity.stateCatalogId === 'lead-gen.company' ? 'company' : 'contact'
|
|
474
|
+
for (const stage of Object.values(LEAD_GEN_STAGE_CATALOG).sort(
|
|
475
|
+
(a, b) => a.order - b.order || a.key.localeCompare(b.key)
|
|
476
|
+
)) {
|
|
477
|
+
if (stage.entity !== leadGenEntity && !(stage.additionalEntities ?? []).includes(leadGenEntity)) continue
|
|
478
|
+
stateEvents.push(buildEntityEventDescriptor(entity, stage.key, stage.label))
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
for (const event of stateEvents) {
|
|
483
|
+
pushEventProjection(
|
|
484
|
+
nodes,
|
|
485
|
+
nodeIds,
|
|
486
|
+
edges,
|
|
487
|
+
edgeIds,
|
|
488
|
+
projectedEventNodeIdsByEventId,
|
|
489
|
+
event,
|
|
490
|
+
id,
|
|
491
|
+
'originates_from'
|
|
492
|
+
)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
for (const { path, system } of systemsWithPaths.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
498
|
+
for (const actionRef of system.actions ?? []) {
|
|
499
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
500
|
+
id: edgeId('uses', nodeId('system', path), nodeId('action', actionRef.actionId), actionRef.intent),
|
|
501
|
+
kind: 'uses',
|
|
502
|
+
sourceId: nodeId('system', path),
|
|
503
|
+
targetId: nodeId('action', actionRef.actionId),
|
|
504
|
+
label: actionRef.intent
|
|
505
|
+
})
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Pre-compute the set of valid system paths so the resource loop below can
|
|
510
|
+
// skip dangling-path edge emission in O(1) per resource. Belt-and-suspenders
|
|
511
|
+
// against the Wave 2 superRefine; reachable only for partial-OM fixture cases.
|
|
512
|
+
const validSystemPaths = new Set([...validSystemRefs])
|
|
513
|
+
|
|
514
|
+
for (const resource of Object.values(organizationModel.resources).sort((a, b) => a.id.localeCompare(b.id))) {
|
|
515
|
+
const resourceNode = upsertResourceNode(nodes, nodeIds, resourceNodesById, {
|
|
516
|
+
id: nodeId('resource', resource.id),
|
|
517
|
+
kind: 'resource',
|
|
518
|
+
label: resource.id,
|
|
519
|
+
sourceId: resource.id,
|
|
520
|
+
resourceType: normalizeOrganizationModelResourceType(resource.kind)
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
// Skip the contains edge if the systemPath doesn't resolve to a real system.
|
|
524
|
+
// In a fully-validated model this never fires; it guards partial-OM fixtures.
|
|
525
|
+
if (validSystemPaths.has(resource.systemPath)) {
|
|
526
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
527
|
+
id: edgeId('contains', systemNodeId(resource.systemPath), resourceNode.id, 'system-resource'),
|
|
528
|
+
kind: 'contains',
|
|
529
|
+
sourceId: systemNodeId(resource.systemPath),
|
|
530
|
+
targetId: resourceNode.id
|
|
531
|
+
})
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (resource.kind === 'workflow' || resource.kind === 'agent') {
|
|
535
|
+
for (const emission of resource.emits ?? []) {
|
|
536
|
+
pushEventProjection(
|
|
537
|
+
nodes,
|
|
538
|
+
nodeIds,
|
|
539
|
+
edges,
|
|
540
|
+
edgeIds,
|
|
541
|
+
projectedEventNodeIdsByEventId,
|
|
542
|
+
buildResourceEventDescriptor(resource.id, emission),
|
|
543
|
+
resourceNode.id,
|
|
544
|
+
'emits'
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (resource.kind === 'agent') {
|
|
550
|
+
for (const [index, invocation] of resource.invocations.entries()) {
|
|
551
|
+
const label = invocationLabel(invocation)
|
|
552
|
+
if (invocation.kind === 'script-execution') {
|
|
553
|
+
const targetNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, invocation.resourceId)
|
|
554
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
555
|
+
id: edgeId('uses', resourceNode.id, targetNode.id, `agent-invocation-${index}`),
|
|
556
|
+
kind: 'uses',
|
|
557
|
+
sourceId: resourceNode.id,
|
|
558
|
+
targetId: targetNode.id,
|
|
559
|
+
label
|
|
560
|
+
})
|
|
561
|
+
continue
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
for (const actionNodeId of actionIdsByInvocation.get(invocationSignature(invocation)) ?? []) {
|
|
565
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
566
|
+
id: edgeId('references', resourceNode.id, actionNodeId, `agent-invocation-${index}`),
|
|
567
|
+
kind: 'references',
|
|
568
|
+
sourceId: resourceNode.id,
|
|
569
|
+
targetId: actionNodeId,
|
|
570
|
+
label
|
|
571
|
+
})
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
for (const policy of Object.values(organizationModel.policies).sort(
|
|
578
|
+
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
579
|
+
)) {
|
|
580
|
+
const id = nodeId('policy', policy.id)
|
|
581
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
582
|
+
id,
|
|
583
|
+
kind: 'policy',
|
|
584
|
+
label: policy.label,
|
|
585
|
+
sourceId: policy.id,
|
|
586
|
+
description: policy.description
|
|
587
|
+
})
|
|
588
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
589
|
+
id: edgeId('contains', organizationNode.id, id),
|
|
590
|
+
kind: 'contains',
|
|
591
|
+
sourceId: organizationNode.id,
|
|
592
|
+
targetId: id
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
for (const systemId of policy.appliesTo.systemIds) {
|
|
596
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
597
|
+
id: edgeId('applies_to', id, systemNodeId(systemId), 'system'),
|
|
598
|
+
kind: 'applies_to',
|
|
599
|
+
sourceId: id,
|
|
600
|
+
targetId: systemNodeId(systemId)
|
|
601
|
+
})
|
|
602
|
+
}
|
|
603
|
+
for (const actionId of policy.appliesTo.actionIds) {
|
|
604
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
605
|
+
id: edgeId('applies_to', id, nodeId('action', actionId), 'action'),
|
|
606
|
+
kind: 'applies_to',
|
|
607
|
+
sourceId: id,
|
|
608
|
+
targetId: nodeId('action', actionId)
|
|
609
|
+
})
|
|
610
|
+
}
|
|
611
|
+
for (const resourceId of policy.appliesTo.resourceIds) {
|
|
612
|
+
const resourceNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, resourceId)
|
|
613
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
614
|
+
id: edgeId('applies_to', id, resourceNode.id, 'resource'),
|
|
615
|
+
kind: 'applies_to',
|
|
616
|
+
sourceId: id,
|
|
617
|
+
targetId: resourceNode.id
|
|
618
|
+
})
|
|
619
|
+
}
|
|
620
|
+
for (const roleId of policy.appliesTo.roleIds) {
|
|
621
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
622
|
+
id: edgeId('applies_to', id, nodeId('role', roleId), 'role'),
|
|
623
|
+
kind: 'applies_to',
|
|
624
|
+
sourceId: id,
|
|
625
|
+
targetId: nodeId('role', roleId)
|
|
626
|
+
})
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (policy.trigger.kind === 'event') {
|
|
630
|
+
const eventNode = projectedEventNodeIdsByEventId.get(policy.trigger.eventId)
|
|
631
|
+
if (eventNode !== undefined) {
|
|
632
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
633
|
+
id: edgeId('triggers', eventNode, id),
|
|
634
|
+
kind: 'triggers',
|
|
635
|
+
sourceId: eventNode,
|
|
636
|
+
targetId: id
|
|
637
|
+
})
|
|
638
|
+
}
|
|
639
|
+
} else if (policy.trigger.kind === 'action-invocation') {
|
|
640
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
641
|
+
id: edgeId('triggers', nodeId('action', policy.trigger.actionId), id),
|
|
642
|
+
kind: 'triggers',
|
|
643
|
+
sourceId: nodeId('action', policy.trigger.actionId),
|
|
644
|
+
targetId: id
|
|
645
|
+
})
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
for (const [effectIndex, effect] of policy.actions.entries()) {
|
|
649
|
+
if (effect.kind === 'invoke-action') {
|
|
650
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
651
|
+
id: edgeId('effects', id, nodeId('action', effect.actionId), `invoke-action-${effectIndex}`),
|
|
652
|
+
kind: 'effects',
|
|
653
|
+
sourceId: id,
|
|
654
|
+
targetId: nodeId('action', effect.actionId),
|
|
655
|
+
label: 'invoke action'
|
|
656
|
+
})
|
|
657
|
+
}
|
|
658
|
+
if ((effect.kind === 'notify-role' || effect.kind === 'require-approval') && effect.roleId !== undefined) {
|
|
659
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
660
|
+
id: edgeId('effects', id, nodeId('role', effect.roleId), `${effect.kind}-${effectIndex}`),
|
|
661
|
+
kind: 'effects',
|
|
662
|
+
sourceId: id,
|
|
663
|
+
targetId: nodeId('role', effect.roleId),
|
|
664
|
+
label: effect.kind
|
|
665
|
+
})
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
for (const segment of Object.values(organizationModel.customers).sort(
|
|
671
|
+
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
672
|
+
)) {
|
|
673
|
+
const id = nodeId('customer-segment', segment.id)
|
|
216
674
|
pushUniqueNode(nodes, nodeIds, {
|
|
217
675
|
id,
|
|
218
|
-
kind: '
|
|
219
|
-
label:
|
|
220
|
-
sourceId:
|
|
221
|
-
description:
|
|
676
|
+
kind: 'customer-segment',
|
|
677
|
+
label: segment.name,
|
|
678
|
+
sourceId: segment.id,
|
|
679
|
+
description: segment.description || undefined
|
|
222
680
|
})
|
|
223
681
|
pushUniqueEdge(edges, edgeIds, {
|
|
224
682
|
id: edgeId('contains', organizationNode.id, id),
|
|
@@ -226,25 +684,194 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
226
684
|
sourceId: organizationNode.id,
|
|
227
685
|
targetId: id
|
|
228
686
|
})
|
|
229
|
-
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
for (const product of Object.values(organizationModel.offerings).sort(
|
|
690
|
+
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
691
|
+
)) {
|
|
692
|
+
const id = nodeId('offering', product.id)
|
|
693
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
694
|
+
id,
|
|
695
|
+
kind: 'offering',
|
|
696
|
+
label: product.name,
|
|
697
|
+
sourceId: product.id,
|
|
698
|
+
description: product.description || undefined
|
|
699
|
+
})
|
|
230
700
|
pushUniqueEdge(edges, edgeIds, {
|
|
231
|
-
id: edgeId('
|
|
232
|
-
kind: '
|
|
233
|
-
sourceId: id,
|
|
234
|
-
targetId:
|
|
701
|
+
id: edgeId('contains', organizationNode.id, id),
|
|
702
|
+
kind: 'contains',
|
|
703
|
+
sourceId: organizationNode.id,
|
|
704
|
+
targetId: id
|
|
705
|
+
})
|
|
706
|
+
for (const segmentId of product.targetSegmentIds) {
|
|
707
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
708
|
+
id: edgeId('applies_to', id, nodeId('customer-segment', segmentId), 'targets-segment'),
|
|
709
|
+
kind: 'applies_to',
|
|
710
|
+
sourceId: id,
|
|
711
|
+
targetId: nodeId('customer-segment', segmentId),
|
|
712
|
+
label: 'targets'
|
|
713
|
+
})
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
for (const objective of Object.values(organizationModel.goals).sort(
|
|
718
|
+
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
719
|
+
)) {
|
|
720
|
+
const id = nodeId('goal', objective.id)
|
|
721
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
722
|
+
id,
|
|
723
|
+
kind: 'goal',
|
|
724
|
+
label: objective.description,
|
|
725
|
+
sourceId: objective.id
|
|
726
|
+
})
|
|
727
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
728
|
+
id: edgeId('contains', organizationNode.id, id),
|
|
729
|
+
kind: 'contains',
|
|
730
|
+
sourceId: organizationNode.id,
|
|
731
|
+
targetId: id
|
|
732
|
+
})
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const sidebarGroups: Array<{
|
|
736
|
+
id: string
|
|
737
|
+
node: Extract<OrganizationModelSidebarNode, { type: 'group' }>
|
|
738
|
+
parentGroupId?: string
|
|
739
|
+
}> = []
|
|
740
|
+
const sidebarSurfaces: Array<{
|
|
741
|
+
id: string
|
|
742
|
+
node: Extract<OrganizationModelSidebarNode, { type: 'surface' }>
|
|
743
|
+
parentGroupId?: string
|
|
744
|
+
}> = []
|
|
745
|
+
collectSidebarGraphNodes(organizationModel.navigation.sidebar.primary, sidebarGroups, sidebarSurfaces)
|
|
746
|
+
collectSidebarGraphNodes(organizationModel.navigation.sidebar.bottom, sidebarGroups, sidebarSurfaces)
|
|
747
|
+
|
|
748
|
+
for (const { id: surfaceSourceId, node: surface, parentGroupId } of sidebarSurfaces) {
|
|
749
|
+
const id = nodeId('surface', surfaceSourceId)
|
|
750
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
751
|
+
id,
|
|
752
|
+
kind: 'surface',
|
|
753
|
+
label: surface.label,
|
|
754
|
+
sourceId: surfaceSourceId,
|
|
755
|
+
description: surface.description,
|
|
756
|
+
icon: surface.icon,
|
|
757
|
+
enabled: true
|
|
758
|
+
})
|
|
759
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
760
|
+
id: edgeId('contains', parentGroupId ? nodeId('navigation-group', parentGroupId) : organizationNode.id, id),
|
|
761
|
+
kind: 'contains',
|
|
762
|
+
sourceId: parentGroupId ? nodeId('navigation-group', parentGroupId) : organizationNode.id,
|
|
763
|
+
targetId: id
|
|
764
|
+
})
|
|
765
|
+
for (const systemId of surface.targets?.systems ?? []) {
|
|
766
|
+
if (!validSystemRefs.has(systemId)) continue
|
|
767
|
+
|
|
768
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
769
|
+
id: edgeId('applies_to', id, systemNodeId(systemId), 'surface-system'),
|
|
770
|
+
kind: 'applies_to',
|
|
771
|
+
sourceId: id,
|
|
772
|
+
targetId: systemNodeId(systemId)
|
|
773
|
+
})
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
for (const { id: groupSourceId, node: group, parentGroupId } of sidebarGroups) {
|
|
778
|
+
const id = nodeId('navigation-group', groupSourceId)
|
|
779
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
780
|
+
id,
|
|
781
|
+
kind: 'navigation-group',
|
|
782
|
+
label: group.label,
|
|
783
|
+
sourceId: groupSourceId
|
|
235
784
|
})
|
|
785
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
786
|
+
id: edgeId('contains', parentGroupId ? nodeId('navigation-group', parentGroupId) : organizationNode.id, id),
|
|
787
|
+
kind: 'contains',
|
|
788
|
+
sourceId: parentGroupId ? nodeId('navigation-group', parentGroupId) : organizationNode.id,
|
|
789
|
+
targetId: id
|
|
790
|
+
})
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// ---------------------------------------------------------------------------
|
|
794
|
+
// Phase 4 Wave 5: content-node graph projection
|
|
795
|
+
// ---------------------------------------------------------------------------
|
|
796
|
+
// For every system in the model tree, emit a 'content-node' graph node for
|
|
797
|
+
// each entry in system.content[*]. Edges:
|
|
798
|
+
// - contains: system spine node → content-node
|
|
799
|
+
// - contains: parent content-node → child content-node (via parentContentId)
|
|
800
|
+
// - references: pipeline content-node → entity node (when data.entityId set)
|
|
801
|
+
// ---------------------------------------------------------------------------
|
|
802
|
+
for (const { path, system } of listAllSystems(organizationModel).sort((a, b) => a.path.localeCompare(b.path))) {
|
|
803
|
+
const contentMap = system.content ?? {}
|
|
804
|
+
const systemSpineId = nodeId('system', path)
|
|
805
|
+
|
|
806
|
+
for (const [localId, contentNode] of Object.entries(contentMap).sort(([a], [b]) => a.localeCompare(b))) {
|
|
807
|
+
const contentNodeGraphId = `content-node:${path}:${localId}`
|
|
808
|
+
|
|
809
|
+
pushUniqueNode(nodes, nodeIds, {
|
|
810
|
+
id: contentNodeGraphId,
|
|
811
|
+
kind: 'content-node',
|
|
812
|
+
label: contentNode.label,
|
|
813
|
+
sourceId: `${path}:${localId}`,
|
|
814
|
+
description: contentNode.description
|
|
815
|
+
// Spread contentKind and contentType into attributes; the node schema
|
|
816
|
+
// does not have custom attribute slots, so we encode them in the label
|
|
817
|
+
// suffix for now. The actual kind/type are recoverable from sourceId +
|
|
818
|
+
// the registry lookup by consumers.
|
|
819
|
+
})
|
|
820
|
+
|
|
821
|
+
// contains: system spine → content-node
|
|
822
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
823
|
+
id: edgeId('contains', systemSpineId, contentNodeGraphId, 'system-content'),
|
|
824
|
+
kind: 'contains',
|
|
825
|
+
sourceId: systemSpineId,
|
|
826
|
+
targetId: contentNodeGraphId
|
|
827
|
+
})
|
|
828
|
+
|
|
829
|
+
// contains: parent content-node → child content-node (parentContentId chain)
|
|
830
|
+
if (contentNode.parentContentId) {
|
|
831
|
+
const parentContentNodeGraphId = `content-node:${path}:${contentNode.parentContentId}`
|
|
832
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
833
|
+
id: edgeId('contains', parentContentNodeGraphId, contentNodeGraphId, 'content-parent'),
|
|
834
|
+
kind: 'contains',
|
|
835
|
+
sourceId: parentContentNodeGraphId,
|
|
836
|
+
targetId: contentNodeGraphId
|
|
837
|
+
})
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// references: pipeline → entity (data.entityId ref-field annotation)
|
|
841
|
+
// schema:pipeline payloadSchema carries `.meta({ ref: 'entity' })` on entityId.
|
|
842
|
+
// Emit a 'references' edge from the pipeline content-node to the entity node.
|
|
843
|
+
if (
|
|
844
|
+
contentNode.kind === 'schema' &&
|
|
845
|
+
contentNode.type === 'pipeline' &&
|
|
846
|
+
contentNode.data &&
|
|
847
|
+
typeof contentNode.data['entityId'] === 'string'
|
|
848
|
+
) {
|
|
849
|
+
const targetEntityId = nodeId('entity', contentNode.data['entityId'])
|
|
850
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
851
|
+
id: edgeId('references', contentNodeGraphId, targetEntityId, 'pipeline-entity'),
|
|
852
|
+
kind: 'references',
|
|
853
|
+
sourceId: contentNodeGraphId,
|
|
854
|
+
targetId: targetEntityId,
|
|
855
|
+
label: 'applies to entity'
|
|
856
|
+
})
|
|
857
|
+
}
|
|
858
|
+
}
|
|
236
859
|
}
|
|
237
860
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
861
|
+
// Phase 4: prospecting domain removed; read templates via migration helper.
|
|
862
|
+
// Steps are typed as BuildTemplate['steps'] which carries ProspectingBuildTemplateStepSchema fields.
|
|
863
|
+
type TemplateStep = { id: string; stageKey: string; actionKey: string; dependsOn?: string[] }
|
|
864
|
+
for (const template of getAllBuildTemplates(organizationModel).sort((a, b) => a.id.localeCompare(b.id))) {
|
|
865
|
+
const steps = template.steps as unknown as TemplateStep[]
|
|
866
|
+
const stepById = new Map(steps.map((s) => [s.id, s]))
|
|
867
|
+
for (const step of [...steps].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
241
868
|
const stageNodeId = nodeId('stage', step.stageKey)
|
|
242
|
-
const
|
|
869
|
+
const actionNodeId = nodeId('action', step.actionKey)
|
|
243
870
|
pushUniqueEdge(edges, edgeIds, {
|
|
244
|
-
id: edgeId('uses', stageNodeId,
|
|
871
|
+
id: edgeId('uses', stageNodeId, actionNodeId, step.id),
|
|
245
872
|
kind: 'uses',
|
|
246
873
|
sourceId: stageNodeId,
|
|
247
|
-
targetId:
|
|
874
|
+
targetId: actionNodeId
|
|
248
875
|
})
|
|
249
876
|
for (const depId of step.dependsOn ?? []) {
|
|
250
877
|
const depStep = stepById.get(depId)
|
|
@@ -277,13 +904,14 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
277
904
|
resourceType: normalizeCommandViewResourceType(resource.type)
|
|
278
905
|
})
|
|
279
906
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
907
|
+
if (!organizationModelResourceIds.has(resource.resourceId)) {
|
|
908
|
+
pushUniqueEdge(edges, edgeIds, {
|
|
909
|
+
id: edgeId('contains', organizationNode.id, resourceNode.id),
|
|
910
|
+
kind: 'contains',
|
|
911
|
+
sourceId: organizationNode.id,
|
|
912
|
+
targetId: resourceNode.id
|
|
913
|
+
})
|
|
914
|
+
}
|
|
287
915
|
}
|
|
288
916
|
|
|
289
917
|
for (const relationship of [...commandViewData.edges].sort((a, b) => a.id.localeCompare(b.id))) {
|