@elevasis/core 0.22.0 → 0.24.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.
Files changed (244) hide show
  1. package/dist/index.d.ts +3214 -2501
  2. package/dist/index.js +3112 -1222
  3. package/dist/knowledge/index.d.ts +1108 -1264
  4. package/dist/knowledge/index.js +112 -9
  5. package/dist/organization-model/index.d.ts +3214 -2501
  6. package/dist/organization-model/index.js +3112 -1222
  7. package/dist/test-utils/index.d.ts +985 -1103
  8. package/dist/test-utils/index.js +2464 -1165
  9. package/package.json +5 -5
  10. package/src/README.md +14 -14
  11. package/src/__tests__/publish.test.ts +24 -24
  12. package/src/__tests__/template-core-compatibility.test.ts +9 -80
  13. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +2389 -2121
  14. package/src/_gen/__tests__/scaffold-contracts.test.ts +30 -30
  15. package/src/auth/multi-tenancy/credentials/__tests__/encryption.test.ts +217 -217
  16. package/src/auth/multi-tenancy/credentials/server/encryption.ts +69 -69
  17. package/src/auth/multi-tenancy/credentials/server/kek-loader.ts +37 -37
  18. package/src/auth/multi-tenancy/index.ts +26 -26
  19. package/src/auth/multi-tenancy/invitations/api-schemas.ts +104 -104
  20. package/src/auth/multi-tenancy/memberships/api-schemas.ts +143 -143
  21. package/src/auth/multi-tenancy/memberships/index.ts +26 -26
  22. package/src/auth/multi-tenancy/memberships/membership.ts +130 -130
  23. package/src/auth/multi-tenancy/organizations/__tests__/api-schemas.test.ts +194 -194
  24. package/src/auth/multi-tenancy/organizations/api-schemas.ts +136 -136
  25. package/src/auth/multi-tenancy/permissions.test.ts +42 -42
  26. package/src/auth/multi-tenancy/permissions.ts +123 -123
  27. package/src/auth/multi-tenancy/role-management/api-schemas.ts +78 -78
  28. package/src/auth/multi-tenancy/role-management/index.ts +16 -16
  29. package/src/auth/multi-tenancy/theme-presets.ts +45 -45
  30. package/src/auth/multi-tenancy/types.ts +57 -57
  31. package/src/auth/multi-tenancy/users/api-schemas.ts +165 -165
  32. package/src/business/README.md +2 -2
  33. package/src/business/acquisition/activity-events.test.ts +250 -250
  34. package/src/business/acquisition/activity-events.ts +93 -93
  35. package/src/business/acquisition/api-schemas.test.ts +1883 -1843
  36. package/src/business/acquisition/api-schemas.ts +1493 -1500
  37. package/src/business/acquisition/build-templates.test.ts +240 -240
  38. package/src/business/acquisition/build-templates.ts +83 -41
  39. package/src/business/acquisition/crm-next-action.test.ts +262 -262
  40. package/src/business/acquisition/crm-next-action.ts +220 -220
  41. package/src/business/acquisition/crm-priority.test.ts +216 -216
  42. package/src/business/acquisition/crm-priority.ts +349 -349
  43. package/src/business/acquisition/crm-state-actions.test.ts +153 -151
  44. package/src/business/acquisition/deal-ownership.test.ts +351 -351
  45. package/src/business/acquisition/deal-ownership.ts +120 -120
  46. package/src/business/acquisition/derive-actions.test.ts +129 -104
  47. package/src/business/acquisition/derive-actions.ts +74 -84
  48. package/src/business/acquisition/index.ts +171 -170
  49. package/src/business/acquisition/ontology-validation.ts +309 -0
  50. package/src/business/acquisition/stateful.ts +30 -30
  51. package/src/business/acquisition/types.ts +396 -392
  52. package/src/business/clients/api-schemas.test.ts +115 -115
  53. package/src/business/clients/api-schemas.ts +158 -158
  54. package/src/business/clients/index.ts +1 -1
  55. package/src/business/crm/api-schemas.ts +40 -40
  56. package/src/business/crm/index.ts +1 -1
  57. package/src/business/deals/api-schemas.ts +87 -87
  58. package/src/business/deals/index.ts +1 -1
  59. package/src/business/index.ts +5 -5
  60. package/src/business/projects/types.ts +144 -144
  61. package/src/commands/queue/types/task.ts +15 -15
  62. package/src/execution/core/runner-types.ts +61 -61
  63. package/src/execution/core/sse-executions.ts +7 -7
  64. package/src/execution/engine/__tests__/fixtures/test-agents.ts +10 -10
  65. package/src/execution/engine/agent/core/__tests__/agent.test.ts +16 -16
  66. package/src/execution/engine/agent/core/__tests__/error-passthrough.test.ts +4 -4
  67. package/src/execution/engine/agent/core/types.ts +25 -25
  68. package/src/execution/engine/agent/index.ts +6 -6
  69. package/src/execution/engine/agent/reasoning/__tests__/request-builder.test.ts +24 -24
  70. package/src/execution/engine/index.ts +443 -443
  71. package/src/execution/engine/tools/integration/server/adapters/apify/__tests__/apify-run-actor.integration.test.ts +298 -298
  72. package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.test.ts +55 -55
  73. package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +107 -107
  74. package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.test.ts +48 -48
  75. package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.ts +99 -99
  76. package/src/execution/engine/tools/integration/server/adapters/apollo/index.ts +1 -1
  77. package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +363 -363
  78. package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.test.ts +162 -162
  79. package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.test.ts +316 -316
  80. package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.test.ts +18 -18
  81. package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.ts +194 -194
  82. package/src/execution/engine/tools/integration/server/adapters/clickup/index.ts +7 -7
  83. package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-adapter.ts +204 -204
  84. package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-tools.ts +105 -105
  85. package/src/execution/engine/tools/integration/server/adapters/google-calendar/google-calendar-adapter.ts +428 -428
  86. package/src/execution/engine/tools/integration/server/adapters/google-calendar/index.ts +2 -2
  87. package/src/execution/engine/tools/integration/server/adapters/google-sheets/__tests__/google-sheets.integration.test.ts +261 -261
  88. package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-tools.ts +1474 -1474
  89. package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-tools.ts +103 -103
  90. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.test.ts +88 -88
  91. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.ts +141 -141
  92. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/utils/types.ts +76 -76
  93. package/src/execution/engine/tools/integration/server/adapters/signature-api/signature-api-tools.ts +182 -182
  94. package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-tools.ts +310 -310
  95. package/src/execution/engine/tools/integration/service.test.ts +239 -239
  96. package/src/execution/engine/tools/integration/service.ts +172 -172
  97. package/src/execution/engine/tools/integration/tool.ts +255 -255
  98. package/src/execution/engine/tools/lead-service-types.ts +1005 -1005
  99. package/src/execution/engine/tools/messages.ts +43 -43
  100. package/src/execution/engine/tools/platform/acquisition/company-tools.ts +7 -7
  101. package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +6 -6
  102. package/src/execution/engine/tools/platform/acquisition/list-tools.ts +6 -6
  103. package/src/execution/engine/tools/platform/acquisition/types.ts +280 -280
  104. package/src/execution/engine/tools/platform/email/types.ts +97 -97
  105. package/src/execution/engine/tools/registry.ts +704 -704
  106. package/src/execution/engine/tools/tool-maps.ts +831 -831
  107. package/src/execution/engine/tools/types.ts +234 -234
  108. package/src/execution/engine/workflow/types.ts +202 -202
  109. package/src/execution/external/__tests__/api-schemas.test.ts +127 -127
  110. package/src/execution/external/api-schemas.ts +40 -40
  111. package/src/execution/external/index.ts +1 -1
  112. package/src/index.ts +18 -18
  113. package/src/integrations/credentials/__tests__/api-schemas.test.ts +420 -420
  114. package/src/integrations/credentials/api-schemas.ts +146 -146
  115. package/src/integrations/credentials/schemas.ts +200 -200
  116. package/src/integrations/oauth/__tests__/provider-registry.test.ts +7 -7
  117. package/src/integrations/oauth/provider-registry.ts +74 -74
  118. package/src/integrations/oauth/server/credentials.ts +43 -43
  119. package/src/integrations/webhook-endpoints/__tests__/api-schemas.test.ts +327 -327
  120. package/src/integrations/webhook-endpoints/api-schemas.ts +103 -103
  121. package/src/integrations/webhook-endpoints/types.ts +58 -58
  122. package/src/knowledge/README.md +33 -32
  123. package/src/knowledge/__tests__/queries.test.ts +633 -541
  124. package/src/knowledge/format.ts +100 -99
  125. package/src/knowledge/index.ts +5 -5
  126. package/src/knowledge/published.ts +5 -5
  127. package/src/knowledge/queries.ts +274 -222
  128. package/src/operations/activities/api-schemas.ts +80 -80
  129. package/src/operations/activities/types.ts +64 -64
  130. package/src/organization-model/README.md +149 -109
  131. package/src/organization-model/__tests__/content-kinds-registry.test.ts +210 -0
  132. package/src/organization-model/__tests__/defaults.test.ts +168 -194
  133. package/src/organization-model/__tests__/domains/actions.test.ts +78 -0
  134. package/src/organization-model/__tests__/domains/customers.test.ts +48 -44
  135. package/src/organization-model/__tests__/domains/entities.test.ts +56 -0
  136. package/src/organization-model/__tests__/domains/goals.test.ts +110 -96
  137. package/src/organization-model/__tests__/domains/identity.test.ts +4 -3
  138. package/src/organization-model/__tests__/domains/navigation.test.ts +222 -166
  139. package/src/organization-model/__tests__/domains/offerings.test.ts +83 -88
  140. package/src/organization-model/__tests__/domains/policies.test.ts +323 -0
  141. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +30 -30
  142. package/src/organization-model/__tests__/domains/resources.test.ts +396 -175
  143. package/src/organization-model/__tests__/domains/roles.test.ts +463 -402
  144. package/src/organization-model/__tests__/domains/statuses.test.ts +13 -10
  145. package/src/organization-model/__tests__/domains/systems.test.ts +209 -193
  146. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +362 -0
  147. package/src/organization-model/__tests__/foundation.test.ts +47 -75
  148. package/src/organization-model/__tests__/get-resources-for-system.test.ts +144 -0
  149. package/src/organization-model/__tests__/graph.test.ts +1336 -149
  150. package/src/organization-model/__tests__/icons.test.ts +10 -1
  151. package/src/organization-model/__tests__/knowledge.test.ts +418 -61
  152. package/src/organization-model/__tests__/lookup-helpers.test.ts +438 -0
  153. package/src/organization-model/__tests__/migration-helpers.test.ts +591 -0
  154. package/src/organization-model/__tests__/prospecting-ssot.test.ts +103 -94
  155. package/src/organization-model/__tests__/recursive-system-schema.test.ts +549 -0
  156. package/src/organization-model/__tests__/resolve.test.ts +303 -42
  157. package/src/organization-model/__tests__/schema.test.ts +863 -153
  158. package/src/organization-model/__tests__/surface-projection.test.ts +284 -174
  159. package/src/organization-model/catalogs/lead-gen.ts +144 -0
  160. package/src/organization-model/content-kinds/config.ts +36 -0
  161. package/src/organization-model/content-kinds/index.ts +78 -0
  162. package/src/organization-model/content-kinds/pipeline.ts +68 -0
  163. package/src/organization-model/content-kinds/registry.ts +44 -0
  164. package/src/organization-model/content-kinds/status.ts +71 -0
  165. package/src/organization-model/content-kinds/template.ts +83 -0
  166. package/src/organization-model/content-kinds/types.ts +117 -0
  167. package/src/organization-model/contracts.ts +27 -17
  168. package/src/organization-model/defaults.ts +489 -107
  169. package/src/organization-model/domains/actions.ts +333 -0
  170. package/src/organization-model/domains/customers.ts +10 -7
  171. package/src/organization-model/domains/entities.ts +144 -0
  172. package/src/organization-model/domains/goals.ts +9 -6
  173. package/src/organization-model/domains/knowledge.ts +128 -54
  174. package/src/organization-model/domains/navigation.ts +139 -416
  175. package/src/organization-model/domains/offerings.ts +15 -10
  176. package/src/organization-model/domains/policies.ts +102 -0
  177. package/src/organization-model/domains/projects.ts +6 -40
  178. package/src/organization-model/domains/prospecting.ts +395 -514
  179. package/src/organization-model/domains/resources.ts +173 -81
  180. package/src/organization-model/domains/roles.ts +96 -93
  181. package/src/organization-model/domains/sales.test.ts +218 -218
  182. package/src/organization-model/domains/sales.ts +380 -589
  183. package/src/organization-model/domains/shared.ts +8 -8
  184. package/src/organization-model/domains/statuses.ts +298 -89
  185. package/src/organization-model/domains/systems.ts +240 -38
  186. package/src/organization-model/foundation.ts +35 -48
  187. package/src/organization-model/graph/build.ts +1035 -279
  188. package/src/organization-model/graph/index.ts +4 -4
  189. package/src/organization-model/graph/link.ts +10 -10
  190. package/src/organization-model/graph/schema.ts +77 -56
  191. package/src/organization-model/graph/types.ts +75 -56
  192. package/src/organization-model/helpers.ts +312 -59
  193. package/src/organization-model/icons.ts +78 -66
  194. package/src/organization-model/index.ts +129 -16
  195. package/src/organization-model/migration-helpers.ts +252 -0
  196. package/src/organization-model/ontology.ts +661 -0
  197. package/src/organization-model/organization-graph.mdx +110 -89
  198. package/src/organization-model/organization-model.mdx +226 -171
  199. package/src/organization-model/published.ts +295 -139
  200. package/src/organization-model/resolve.ts +139 -21
  201. package/src/organization-model/schema.ts +841 -301
  202. package/src/organization-model/surface-projection.ts +212 -218
  203. package/src/organization-model/types.ts +181 -90
  204. package/src/platform/api/types.ts +38 -38
  205. package/src/platform/constants/versions.ts +3 -3
  206. package/src/platform/index.ts +23 -23
  207. package/src/platform/registry/__tests__/command-view.test.ts +5 -7
  208. package/src/platform/registry/__tests__/resource-link.test.ts +35 -30
  209. package/src/platform/registry/__tests__/resource-registry.integration.test.ts +17 -32
  210. package/src/platform/registry/__tests__/resource-registry.nested-systems.test.ts +245 -0
  211. package/src/platform/registry/__tests__/resource-registry.test.ts +2053 -2051
  212. package/src/platform/registry/__tests__/validation.test.ts +1347 -1343
  213. package/src/platform/registry/command-view.ts +10 -10
  214. package/src/platform/registry/index.ts +103 -103
  215. package/src/platform/registry/resource-link.ts +32 -32
  216. package/src/platform/registry/resource-registry.ts +890 -878
  217. package/src/platform/registry/serialization.ts +295 -295
  218. package/src/platform/registry/serialized-types.ts +166 -166
  219. package/src/platform/registry/stats-types.ts +68 -68
  220. package/src/platform/registry/types.ts +425 -425
  221. package/src/platform/registry/validation.ts +745 -743
  222. package/src/platform/utils/__tests__/validation.test.ts +1084 -1084
  223. package/src/platform/utils/validation.ts +425 -425
  224. package/src/projects/api-schemas.test.ts +39 -39
  225. package/src/projects/api-schemas.ts +291 -291
  226. package/src/reference/_generated/contracts.md +2389 -2121
  227. package/src/reference/glossary.md +76 -76
  228. package/src/scaffold-registry/__tests__/index.test.ts +206 -206
  229. package/src/scaffold-registry/__tests__/schema.test.ts +166 -166
  230. package/src/scaffold-registry/index.ts +392 -392
  231. package/src/scaffold-registry/schema.ts +243 -243
  232. package/src/server.ts +289 -289
  233. package/src/supabase/database.types.ts +3153 -3093
  234. package/src/test-utils/README.md +37 -37
  235. package/src/test-utils/entities.ts +108 -108
  236. package/src/test-utils/fixtures/memberships.ts +82 -82
  237. package/src/test-utils/index.ts +12 -12
  238. package/src/test-utils/organization-model.ts +65 -65
  239. package/src/test-utils/published.ts +6 -6
  240. package/src/test-utils/rls/RLSTestContext.ts +588 -588
  241. package/src/test-utils/test-utils.test.ts +44 -49
  242. package/src/organization-model/__tests__/domains/operations.test.ts +0 -203
  243. package/src/organization-model/domains/features.ts +0 -31
  244. package/src/organization-model/domains/operations.ts +0 -85
@@ -1,325 +1,1081 @@
1
- import { BuildOrganizationGraphInputSchema, OrganizationGraphSchema } from './schema'
2
- import type { CommandViewData } from '../../platform/registry/command-view'
3
- import type {
4
- BuildOrganizationGraphInput,
1
+ import { BuildOrganizationGraphInputSchema, OrganizationGraphSchema } from './schema'
2
+ import type { CommandViewData } from '../../platform/registry/command-view'
3
+ import type {
4
+ BuildOrganizationGraphInput,
5
5
  OrganizationGraph,
6
6
  OrganizationGraphEdge,
7
+ OrganizationGraphEdgeKind,
7
8
  OrganizationGraphNode,
8
9
  OrganizationGraphNodeKind
9
10
  } from './types'
10
- import { CAPABILITY_REGISTRY } from '../domains/prospecting'
11
-
12
- function nodeId(kind: OrganizationGraphNodeKind, sourceId?: string): string {
13
- return kind === 'organization' ? 'organization-model' : `${kind}:${sourceId ?? ''}`
14
- }
15
-
16
- function edgeId(kind: string, sourceId: string, targetId: string, variant?: string): string {
17
- return variant ? `edge:${kind}:${variant}:${sourceId}:${targetId}` : `edge:${kind}:${sourceId}:${targetId}`
11
+ import type { ActionInvocation } from '../domains/actions'
12
+ import type { Entity } from '../domains/entities'
13
+ import type { ResourceEntry } from '../domains/resources'
14
+ import type { EventDescriptor, OrganizationModelSidebarNode } from '../types'
15
+ import {
16
+ getAllPipelines,
17
+ getAllBuildTemplates,
18
+ getAllProjectStatuses,
19
+ getAllProspectingStages
20
+ } from '../migration-helpers'
21
+ import { LEAD_GEN_STAGE_CATALOG } from '../catalogs/lead-gen'
22
+ import { listAllSystems } from '../helpers'
23
+ import {
24
+ compileOrganizationOntology,
25
+ listResolvedOntologyRecords,
26
+ ontologyGraphNodeId,
27
+ parseOntologyId,
28
+ type OntologyActionType,
29
+ type OntologyCatalogType,
30
+ type OntologyGroup,
31
+ type OntologyId,
32
+ type OntologyLinkType
33
+ } from '../ontology'
34
+
35
+ type EventEmissionDescriptor = NonNullable<Extract<ResourceEntry, { kind: 'workflow' }>['emits']>[number]
36
+
37
+ function nodeId(kind: OrganizationGraphNodeKind, sourceId?: string): string {
38
+ return kind === 'organization' ? 'organization-model' : `${kind}:${sourceId ?? ''}`
39
+ }
40
+
41
+ function edgeId(kind: string, sourceId: string, targetId: string, variant?: string): string {
42
+ return variant ? `edge:${kind}:${variant}:${sourceId}:${targetId}` : `edge:${kind}:${sourceId}:${targetId}`
43
+ }
44
+
45
+ function pushUniqueNode(nodes: OrganizationGraphNode[], seen: Set<string>, node: OrganizationGraphNode): void {
46
+ if (seen.has(node.id)) return
47
+ seen.add(node.id)
48
+ nodes.push(node)
49
+ }
50
+
51
+ function pushUniqueEdge(edges: OrganizationGraphEdge[], seen: Set<string>, edge: OrganizationGraphEdge): void {
52
+ if (seen.has(edge.id)) return
53
+ seen.add(edge.id)
54
+ edges.push(edge)
55
+ }
56
+
57
+ function collectSidebarGraphNodes(
58
+ nodes: Record<string, OrganizationModelSidebarNode>,
59
+ groups: Array<{
60
+ id: string
61
+ node: Extract<OrganizationModelSidebarNode, { type: 'group' }>
62
+ parentGroupId?: string
63
+ }>,
64
+ surfaces: Array<{
65
+ id: string
66
+ node: Extract<OrganizationModelSidebarNode, { type: 'surface' }>
67
+ parentGroupId?: string
68
+ }>,
69
+ parentGroupId?: string
70
+ ): void {
71
+ Object.entries(nodes)
72
+ .sort(([leftId, left], [rightId, right]) => {
73
+ const orderDelta = (left.order ?? Number.MAX_SAFE_INTEGER) - (right.order ?? Number.MAX_SAFE_INTEGER)
74
+ return orderDelta === 0 ? leftId.localeCompare(rightId) : orderDelta
75
+ })
76
+ .forEach(([id, node]) => {
77
+ if (node.type === 'group') {
78
+ groups.push({ id, node, ...(parentGroupId !== undefined ? { parentGroupId } : {}) })
79
+ collectSidebarGraphNodes(node.children, groups, surfaces, id)
80
+ return
81
+ }
82
+
83
+ surfaces.push({ id, node, ...(parentGroupId !== undefined ? { parentGroupId } : {}) })
84
+ })
85
+ }
86
+
87
+ function upsertResourceNode(
88
+ nodes: OrganizationGraphNode[],
89
+ seen: Set<string>,
90
+ resourceNodesById: Map<string, OrganizationGraphNode>,
91
+ node: OrganizationGraphNode
92
+ ): OrganizationGraphNode {
93
+ const existing = resourceNodesById.get(node.id)
94
+ if (existing) {
95
+ if (!existing.label || existing.label === existing.sourceId) {
96
+ existing.label = node.label
97
+ }
98
+ if (!existing.description && node.description) {
99
+ existing.description = node.description
100
+ }
101
+ if (!existing.sourceId && node.sourceId) {
102
+ existing.sourceId = node.sourceId
103
+ }
104
+ if (!existing.resourceType && node.resourceType) {
105
+ existing.resourceType = node.resourceType
106
+ }
107
+ return existing
108
+ }
109
+
110
+ resourceNodesById.set(node.id, node)
111
+ pushUniqueNode(nodes, seen, node)
112
+ return node
113
+ }
114
+
115
+ function ensureResourceNode(
116
+ nodes: OrganizationGraphNode[],
117
+ seen: Set<string>,
118
+ resourceNodesById: Map<string, OrganizationGraphNode>,
119
+ resourceId: string
120
+ ): OrganizationGraphNode {
121
+ const existing = resourceNodesById.get(nodeId('resource', resourceId))
122
+ if (existing) return existing
123
+
124
+ return upsertResourceNode(nodes, seen, resourceNodesById, {
125
+ id: nodeId('resource', resourceId),
126
+ kind: 'resource',
127
+ label: resourceId,
128
+ sourceId: resourceId
129
+ })
130
+ }
131
+
132
+ type CommandViewResource =
133
+ | CommandViewData['workflows'][number]
134
+ | CommandViewData['agents'][number]
135
+ | CommandViewData['triggers'][number]
136
+ | CommandViewData['integrations'][number]
137
+ | CommandViewData['externalResources'][number]
138
+ | CommandViewData['humanCheckpoints'][number]
139
+
140
+ function normalizeCommandViewResourceType(
141
+ resourceType: CommandViewResource['type']
142
+ ): OrganizationGraphNode['resourceType'] {
143
+ return resourceType === 'human' ? 'human_checkpoint' : resourceType
144
+ }
145
+
146
+ function normalizeOrganizationModelResourceType(
147
+ resourceType: ResourceEntry['kind']
148
+ ): OrganizationGraphNode['resourceType'] {
149
+ return resourceType
150
+ }
151
+
152
+ function collectCommandViewResources(commandViewData: CommandViewData): CommandViewResource[] {
153
+ return [
154
+ ...commandViewData.workflows,
155
+ ...commandViewData.agents,
156
+ ...commandViewData.triggers,
157
+ ...commandViewData.integrations,
158
+ ...commandViewData.externalResources,
159
+ ...commandViewData.humanCheckpoints
160
+ ]
161
+ }
162
+
163
+ function invocationSignature(invocation: ActionInvocation): string {
164
+ switch (invocation.kind) {
165
+ case 'slash-command':
166
+ return `${invocation.kind}:${invocation.command}`
167
+ case 'mcp-tool':
168
+ return `${invocation.kind}:${invocation.server}:${invocation.name}`
169
+ case 'api-endpoint':
170
+ return `${invocation.kind}:${invocation.method}:${invocation.path}`
171
+ case 'script-execution':
172
+ return `${invocation.kind}:${invocation.resourceId}`
173
+ }
174
+ }
175
+
176
+ function invocationLabel(invocation: ActionInvocation): string {
177
+ switch (invocation.kind) {
178
+ case 'slash-command':
179
+ return invocation.command
180
+ case 'mcp-tool':
181
+ return `${invocation.server}.${invocation.name}`
182
+ case 'api-endpoint':
183
+ return `${invocation.method} ${invocation.path}`
184
+ case 'script-execution':
185
+ return `script ${invocation.resourceId}`
186
+ }
187
+ }
188
+
189
+ function eventNodeId(eventId: string): string {
190
+ return nodeId('event', eventId)
18
191
  }
19
192
 
20
- function pushUniqueNode(nodes: OrganizationGraphNode[], seen: Set<string>, node: OrganizationGraphNode): void {
21
- if (seen.has(node.id)) return
22
- seen.add(node.id)
23
- nodes.push(node)
193
+ function ontologyLabel(id: OntologyId, label: string | undefined): string {
194
+ return label ?? parseOntologyId(id).localId
24
195
  }
25
196
 
26
- function pushUniqueEdge(edges: OrganizationGraphEdge[], seen: Set<string>, edge: OrganizationGraphEdge): void {
27
- if (seen.has(edge.id)) return
28
- seen.add(edge.id)
29
- edges.push(edge)
30
- }
31
-
32
- function upsertResourceNode(
33
- nodes: OrganizationGraphNode[],
34
- seen: Set<string>,
35
- resourceNodesById: Map<string, OrganizationGraphNode>,
36
- node: OrganizationGraphNode
37
- ): OrganizationGraphNode {
38
- const existing = resourceNodesById.get(node.id)
39
- if (existing) {
40
- if (!existing.label || existing.label === existing.sourceId) {
41
- existing.label = node.label
42
- }
43
- if (!existing.description && node.description) {
44
- existing.description = node.description
45
- }
46
- if (!existing.sourceId && node.sourceId) {
47
- existing.sourceId = node.sourceId
48
- }
49
- if (!existing.resourceType && node.resourceType) {
50
- existing.resourceType = node.resourceType
51
- }
52
- return existing
53
- }
54
-
55
- resourceNodesById.set(node.id, node)
56
- pushUniqueNode(nodes, seen, node)
57
- return node
58
- }
59
-
60
- function ensureResourceNode(
61
- nodes: OrganizationGraphNode[],
62
- seen: Set<string>,
63
- resourceNodesById: Map<string, OrganizationGraphNode>,
64
- resourceId: string
65
- ): OrganizationGraphNode {
66
- const existing = resourceNodesById.get(nodeId('resource', resourceId))
67
- if (existing) return existing
68
-
69
- return upsertResourceNode(nodes, seen, resourceNodesById, {
70
- id: nodeId('resource', resourceId),
71
- kind: 'resource',
72
- label: resourceId,
73
- sourceId: resourceId
74
- })
75
- }
76
-
77
- type CommandViewResource =
78
- | CommandViewData['workflows'][number]
79
- | CommandViewData['agents'][number]
80
- | CommandViewData['triggers'][number]
81
- | CommandViewData['integrations'][number]
82
- | CommandViewData['externalResources'][number]
83
- | CommandViewData['humanCheckpoints'][number]
84
-
85
- function normalizeCommandViewResourceType(
86
- resourceType: CommandViewResource['type']
87
- ): OrganizationGraphNode['resourceType'] {
88
- return resourceType === 'human' ? 'human_checkpoint' : resourceType
89
- }
90
-
91
- function collectCommandViewResources(commandViewData: CommandViewData): CommandViewResource[] {
92
- return [
93
- ...commandViewData.workflows,
94
- ...commandViewData.agents,
95
- ...commandViewData.triggers,
96
- ...commandViewData.integrations,
97
- ...commandViewData.externalResources,
98
- ...commandViewData.humanCheckpoints
99
- ]
100
- }
101
-
102
- function pushResourceLinks(
197
+ function pushOntologyBindingEdges(
103
198
  edges: OrganizationGraphEdge[],
104
199
  edgeIds: Set<string>,
105
200
  resourceNodeId: string,
106
- links: CommandViewResource['links'] | undefined
201
+ kind: OrganizationGraphEdgeKind,
202
+ ids: OntologyId[] | undefined
107
203
  ): void {
108
- for (const link of links ?? []) {
204
+ ids?.forEach((ontologyId) => {
205
+ const targetId = ontologyGraphNodeId(ontologyId)
109
206
  pushUniqueEdge(edges, edgeIds, {
110
- id: edgeId(link.kind, resourceNodeId, link.nodeId),
111
- kind: link.kind,
207
+ id: edgeId(kind, resourceNodeId, targetId),
208
+ kind,
112
209
  sourceId: resourceNodeId,
113
- targetId: link.nodeId
210
+ targetId
114
211
  })
115
- }
212
+ })
116
213
  }
117
-
118
- export function buildOrganizationGraph(input: BuildOrganizationGraphInput): OrganizationGraph {
119
- const parsed = BuildOrganizationGraphInputSchema.parse(input)
120
- const organizationModel = parsed.organizationModel
121
- const commandViewData = parsed.commandViewData as CommandViewData | undefined
122
-
123
- const nodes: OrganizationGraphNode[] = []
124
- const edges: OrganizationGraphEdge[] = []
125
- const nodeIds = new Set<string>()
126
- const edgeIds = new Set<string>()
127
- const resourceNodesById = new Map<string, OrganizationGraphNode>()
128
-
129
- const organizationNode: OrganizationGraphNode = {
130
- id: nodeId('organization'),
131
- kind: 'organization',
132
- label: 'Organization Model'
214
+
215
+ function buildResourceEventDescriptor(resourceId: string, emission: EventEmissionDescriptor): EventDescriptor {
216
+ return {
217
+ ...emission,
218
+ id: `${resourceId}:${emission.eventKey}`,
219
+ ownerId: resourceId,
220
+ ownerKind: 'resource'
221
+ }
222
+ }
223
+
224
+ function buildEntityEventDescriptor(entity: Entity, eventKey: string, label: string): EventDescriptor {
225
+ return {
226
+ id: `${entity.id}:${eventKey}`,
227
+ ownerId: entity.id,
228
+ ownerKind: 'entity',
229
+ eventKey,
230
+ label
231
+ }
232
+ }
233
+
234
+ function pushEventProjection(
235
+ nodes: OrganizationGraphNode[],
236
+ nodeIds: Set<string>,
237
+ edges: OrganizationGraphEdge[],
238
+ edgeIds: Set<string>,
239
+ eventNodeIdsByEventId: Map<string, string>,
240
+ event: EventDescriptor,
241
+ sourceNodeId: string,
242
+ edgeKind: 'emits' | 'originates_from'
243
+ ): void {
244
+ const id = eventNodeId(event.id)
245
+ eventNodeIdsByEventId.set(event.id, id)
246
+ pushUniqueNode(nodes, nodeIds, {
247
+ id,
248
+ kind: 'event',
249
+ label: event.label,
250
+ sourceId: event.id
251
+ })
252
+ const sourceId = edgeKind === 'originates_from' ? id : sourceNodeId
253
+ const targetId = edgeKind === 'originates_from' ? sourceNodeId : id
254
+ pushUniqueEdge(edges, edgeIds, {
255
+ id: edgeId(edgeKind, sourceId, targetId),
256
+ kind: edgeKind,
257
+ sourceId,
258
+ targetId
259
+ })
260
+ }
261
+
262
+ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): OrganizationGraph {
263
+ const parsed = BuildOrganizationGraphInputSchema.parse(input)
264
+ const organizationModel = parsed.organizationModel
265
+ const commandViewData = parsed.commandViewData as CommandViewData | undefined
266
+
267
+ const nodes: OrganizationGraphNode[] = []
268
+ const edges: OrganizationGraphEdge[] = []
269
+ const nodeIds = new Set<string>()
270
+ const edgeIds = new Set<string>()
271
+ const resourceNodesById = new Map<string, OrganizationGraphNode>()
272
+ const organizationModelResourceIds = new Set(Object.keys(organizationModel.resources))
273
+ const actionIdsByInvocation = new Map<string, string[]>()
274
+ const projectedEventNodeIdsByEventId = new Map<string, string>()
275
+ const ontologyCompilation = compileOrganizationOntology(organizationModel)
276
+
277
+ const organizationNode: OrganizationGraphNode = {
278
+ id: nodeId('organization'),
279
+ kind: 'organization',
280
+ label: 'Organization Model'
281
+ }
282
+ pushUniqueNode(nodes, nodeIds, organizationNode)
283
+ const systemsWithPaths = listAllSystems(organizationModel)
284
+ const systemPathByRef = new Map<string, string>()
285
+ for (const { path, system } of systemsWithPaths) {
286
+ systemPathByRef.set(path, path)
287
+ systemPathByRef.set(system.id, path)
288
+ }
289
+ const validSystemRefs = new Set(systemPathByRef.keys())
290
+ const systemNodeId = (systemRef: string) => nodeId('system', systemPathByRef.get(systemRef) ?? systemRef)
291
+
292
+ for (const { path, system } of systemsWithPaths.sort((a, b) => a.path.localeCompare(b.path))) {
293
+ const id = nodeId('system', path)
294
+ pushUniqueNode(nodes, nodeIds, {
295
+ id,
296
+ kind: 'system',
297
+ label: system.label ?? system.title ?? system.id,
298
+ sourceId: path,
299
+ description: system.description,
300
+ icon: system.ui?.icon ?? system.icon,
301
+ enabled: system.enabled
302
+ })
303
+ pushUniqueEdge(edges, edgeIds, {
304
+ id: edgeId('contains', organizationNode.id, id),
305
+ kind: 'contains',
306
+ sourceId: organizationNode.id,
307
+ targetId: id
308
+ })
309
+ const parentSystemId = path.includes('.') ? path.slice(0, path.lastIndexOf('.')) : undefined
310
+ if (parentSystemId !== undefined) {
311
+ pushUniqueEdge(edges, edgeIds, {
312
+ id: edgeId('contains', nodeId('system', parentSystemId), id),
313
+ kind: 'contains',
314
+ sourceId: nodeId('system', parentSystemId),
315
+ targetId: id
316
+ })
317
+ }
133
318
  }
134
- pushUniqueNode(nodes, nodeIds, organizationNode)
135
319
 
136
- for (const feature of [...organizationModel.features].sort((a, b) => a.id.localeCompare(b.id))) {
137
- const id = nodeId('feature', feature.id)
320
+ for (const { id: ontologyId, kind, record } of listResolvedOntologyRecords(ontologyCompilation.ontology).sort((a, b) =>
321
+ a.id.localeCompare(b.id)
322
+ )) {
323
+ const id = ontologyGraphNodeId(ontologyId)
324
+ const parsedId = parseOntologyId(ontologyId)
138
325
  pushUniqueNode(nodes, nodeIds, {
139
326
  id,
140
- kind: 'feature',
141
- label: feature.label,
142
- sourceId: feature.id,
143
- description: feature.description,
144
- icon: feature.icon,
145
- enabled: feature.enabled,
146
- featureId: feature.id
147
- })
148
- pushUniqueEdge(edges, edgeIds, {
149
- id: edgeId('contains', organizationNode.id, id),
150
- kind: 'contains',
151
- sourceId: organizationNode.id,
152
- targetId: id
327
+ kind: 'ontology',
328
+ label: ontologyLabel(ontologyId, record.label),
329
+ sourceId: ontologyId,
330
+ description: record.description,
331
+ ontologyKind: kind
153
332
  })
154
- const parentId = feature.id.includes('.') ? feature.id.slice(0, feature.id.lastIndexOf('.')) : undefined
155
- if (parentId) {
333
+
334
+ const ownerSystemId = record.ownerSystemId ?? (parsedId.isGlobal ? undefined : parsedId.scope)
335
+ if (ownerSystemId !== undefined && validSystemRefs.has(ownerSystemId)) {
156
336
  pushUniqueEdge(edges, edgeIds, {
157
- id: edgeId('contains', nodeId('feature', parentId), id),
337
+ id: edgeId('contains', systemNodeId(ownerSystemId), id, `ontology-${kind}`),
158
338
  kind: 'contains',
159
- sourceId: nodeId('feature', parentId),
339
+ sourceId: systemNodeId(ownerSystemId),
160
340
  targetId: id
161
341
  })
162
- }
163
- }
164
-
165
- for (const node of [...(organizationModel.knowledge?.nodes ?? [])].sort((a, b) => a.id.localeCompare(b.id))) {
166
- const id = nodeId('knowledge', node.id)
167
- pushUniqueNode(nodes, nodeIds, {
168
- id,
169
- kind: 'knowledge',
170
- label: node.title,
171
- sourceId: node.id,
172
- description: node.summary,
173
- icon: node.icon
174
- })
175
- pushUniqueEdge(edges, edgeIds, {
176
- id: edgeId('contains', organizationNode.id, id),
177
- kind: 'contains',
178
- sourceId: organizationNode.id,
179
- targetId: id
180
- })
181
- for (const link of node.links) {
342
+ } else {
182
343
  pushUniqueEdge(edges, edgeIds, {
183
- id: edgeId('governs', id, link.nodeId),
184
- kind: 'governs',
185
- sourceId: id,
186
- targetId: link.nodeId
344
+ id: edgeId('contains', organizationNode.id, id, `ontology-${kind}`),
345
+ kind: 'contains',
346
+ sourceId: organizationNode.id,
347
+ targetId: id
187
348
  })
188
349
  }
189
- }
190
350
 
191
- const allStages = [
192
- ...organizationModel.prospecting.companyStages,
193
- ...organizationModel.prospecting.contactStages
194
- ].sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))
195
-
196
- for (const stage of allStages) {
197
- const id = nodeId('stage', stage.id)
198
- pushUniqueNode(nodes, nodeIds, {
199
- id,
200
- kind: 'stage',
201
- label: stage.label,
202
- sourceId: stage.id,
203
- ...(stage.description ? { description: stage.description } : {}),
204
- ...(stage.icon ? { icon: stage.icon } : {})
205
- })
206
- pushUniqueEdge(edges, edgeIds, {
207
- id: edgeId('contains', organizationNode.id, id),
208
- kind: 'contains',
209
- sourceId: organizationNode.id,
210
- targetId: id
211
- })
212
- }
213
-
214
- for (const cap of [...CAPABILITY_REGISTRY].sort((a, b) => a.id.localeCompare(b.id))) {
215
- const id = nodeId('capability', cap.id)
216
- pushUniqueNode(nodes, nodeIds, {
217
- id,
218
- kind: 'capability',
219
- label: cap.label,
220
- sourceId: cap.id,
221
- description: cap.description
222
- })
223
- pushUniqueEdge(edges, edgeIds, {
224
- id: edgeId('contains', organizationNode.id, id),
225
- kind: 'contains',
226
- sourceId: organizationNode.id,
227
- targetId: id
228
- })
229
- const resourceNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, cap.resourceId)
230
- pushUniqueEdge(edges, edgeIds, {
231
- id: edgeId('maps_to', id, resourceNode.id),
232
- kind: 'maps_to',
233
- sourceId: id,
234
- targetId: resourceNode.id
235
- })
236
- }
237
-
238
- for (const template of [...organizationModel.prospecting.buildTemplates].sort((a, b) => a.id.localeCompare(b.id))) {
239
- const stepById = new Map(template.steps.map((s) => [s.id, s]))
240
- for (const step of [...template.steps].sort((a, b) => a.id.localeCompare(b.id))) {
241
- const stageNodeId = nodeId('stage', step.stageKey)
242
- const capNodeId = nodeId('capability', step.capabilityKey)
351
+ if (kind === 'link') {
352
+ const link = record as OntologyLinkType
243
353
  pushUniqueEdge(edges, edgeIds, {
244
- id: edgeId('uses', stageNodeId, capNodeId, step.id),
245
- kind: 'uses',
246
- sourceId: stageNodeId,
247
- targetId: capNodeId
354
+ id: edgeId('links', ontologyGraphNodeId(link.from), ontologyGraphNodeId(link.to), ontologyId),
355
+ kind: 'links',
356
+ sourceId: ontologyGraphNodeId(link.from),
357
+ targetId: ontologyGraphNodeId(link.to),
358
+ label: link.label ?? parsedId.localId
248
359
  })
249
- for (const depId of step.dependsOn ?? []) {
250
- const depStep = stepById.get(depId)
251
- if (depStep) {
252
- const depStageNodeId = nodeId('stage', depStep.stageKey)
253
- pushUniqueEdge(edges, edgeIds, {
254
- id: edgeId('references', stageNodeId, depStageNodeId, step.id),
255
- kind: 'references',
256
- sourceId: stageNodeId,
257
- targetId: depStageNodeId
258
- })
259
- }
260
- }
261
360
  }
262
- }
263
-
264
- if (commandViewData) {
265
- const commandViewResources = collectCommandViewResources(commandViewData).sort((a, b) =>
266
- a.resourceId.localeCompare(b.resourceId)
267
- )
268
361
 
269
- for (const resource of commandViewResources) {
270
- const id = nodeId('resource', resource.resourceId)
271
- const resourceNode = upsertResourceNode(nodes, nodeIds, resourceNodesById, {
272
- id,
273
- kind: 'resource',
274
- label: resource.name,
275
- sourceId: resource.resourceId,
276
- description: resource.description,
277
- resourceType: normalizeCommandViewResourceType(resource.type)
278
- })
362
+ if (kind === 'action') {
363
+ const action = record as OntologyActionType
364
+ for (const targetOntologyId of action.actsOn ?? []) {
365
+ pushUniqueEdge(edges, edgeIds, {
366
+ id: edgeId('affects', id, ontologyGraphNodeId(targetOntologyId), ontologyId),
367
+ kind: 'affects',
368
+ sourceId: id,
369
+ targetId: ontologyGraphNodeId(targetOntologyId)
370
+ })
371
+ }
372
+ }
279
373
 
374
+ if (kind === 'catalog') {
375
+ const catalog = record as OntologyCatalogType
376
+ if (catalog.appliesTo === undefined) continue
280
377
  pushUniqueEdge(edges, edgeIds, {
281
- id: edgeId('contains', organizationNode.id, resourceNode.id),
282
- kind: 'contains',
283
- sourceId: organizationNode.id,
284
- targetId: resourceNode.id
378
+ id: edgeId('applies_to', id, ontologyGraphNodeId(catalog.appliesTo), ontologyId),
379
+ kind: 'applies_to',
380
+ sourceId: id,
381
+ targetId: ontologyGraphNodeId(catalog.appliesTo)
285
382
  })
286
- pushResourceLinks(edges, edgeIds, resourceNode.id, resource.links)
287
383
  }
288
384
 
289
- for (const relationship of [...commandViewData.edges].sort((a, b) => a.id.localeCompare(b.id))) {
290
- const sourceNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, relationship.source)
291
- const targetNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, relationship.target)
292
-
385
+ if (kind === 'group') {
386
+ const group = record as OntologyGroup
387
+ for (const memberOntologyId of group.members ?? []) {
388
+ pushUniqueEdge(edges, edgeIds, {
389
+ id: edgeId('contains', id, ontologyGraphNodeId(memberOntologyId), ontologyId),
390
+ kind: 'contains',
391
+ sourceId: id,
392
+ targetId: ontologyGraphNodeId(memberOntologyId)
393
+ })
394
+ }
395
+ }
396
+ }
397
+
398
+ for (const role of Object.values(organizationModel.roles).sort((a, b) => a.id.localeCompare(b.id))) {
399
+ const id = nodeId('role', role.id)
400
+ pushUniqueNode(nodes, nodeIds, {
401
+ id,
402
+ kind: 'role',
403
+ label: role.title,
404
+ sourceId: role.id,
405
+ description: role.responsibilities.length > 0 ? role.responsibilities.join('; ') : undefined
406
+ })
407
+ pushUniqueEdge(edges, edgeIds, {
408
+ id: edgeId('contains', organizationNode.id, id),
409
+ kind: 'contains',
410
+ sourceId: organizationNode.id,
411
+ targetId: id
412
+ })
413
+ if (role.reportsToId !== undefined) {
414
+ pushUniqueEdge(edges, edgeIds, {
415
+ id: edgeId('references', id, nodeId('role', role.reportsToId), 'reports-to'),
416
+ kind: 'references',
417
+ sourceId: id,
418
+ targetId: nodeId('role', role.reportsToId),
419
+ label: 'reports to'
420
+ })
421
+ }
422
+ for (const systemId of role.responsibleFor ?? []) {
423
+ pushUniqueEdge(edges, edgeIds, {
424
+ id: edgeId('governs', id, systemNodeId(systemId), 'responsible-for'),
425
+ kind: 'governs',
426
+ sourceId: id,
427
+ targetId: systemNodeId(systemId),
428
+ label: 'responsible for'
429
+ })
430
+ }
431
+ }
432
+
433
+ // Phase 4: knowledge is now Record<id, OrgKnowledgeNode> (D3); iterate Object.values.
434
+ for (const node of Object.values(organizationModel.knowledge).sort((a, b) => a.id.localeCompare(b.id))) {
435
+ const id = nodeId('knowledge', node.id)
436
+ pushUniqueNode(nodes, nodeIds, {
437
+ id,
438
+ kind: 'knowledge',
439
+ label: node.title,
440
+ sourceId: node.id,
441
+ description: node.summary,
442
+ icon: node.icon
443
+ })
444
+ pushUniqueEdge(edges, edgeIds, {
445
+ id: edgeId('contains', organizationNode.id, id),
446
+ kind: 'contains',
447
+ sourceId: organizationNode.id,
448
+ targetId: id
449
+ })
450
+ for (const link of node.links) {
451
+ const targetId =
452
+ link.target.kind === 'system'
453
+ ? systemNodeId(link.target.id)
454
+ : link.target.kind === 'ontology'
455
+ ? ontologyGraphNodeId(link.target.id)
456
+ : link.nodeId
293
457
  pushUniqueEdge(edges, edgeIds, {
294
- id: edgeId('contains', organizationNode.id, sourceNode.id),
295
- kind: 'contains',
296
- sourceId: organizationNode.id,
297
- targetId: sourceNode.id
298
- })
458
+ id: edgeId('governs', id, targetId),
459
+ kind: 'governs',
460
+ sourceId: id,
461
+ targetId
462
+ })
463
+ }
464
+ }
465
+
466
+ // Phase 4: prospecting domain removed; read stages via migration helper.
467
+ const allStages = [
468
+ ...getAllProspectingStages(organizationModel, 'company'),
469
+ ...getAllProspectingStages(organizationModel, 'contact')
470
+ ].sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))
471
+
472
+ for (const stage of allStages) {
473
+ const id = nodeId('stage', stage.id)
474
+ pushUniqueNode(nodes, nodeIds, {
475
+ id,
476
+ kind: 'stage',
477
+ label: stage.label,
478
+ sourceId: stage.id,
479
+ ...(stage.description ? { description: stage.description } : {}),
480
+ ...(stage.icon ? { icon: stage.icon } : {})
481
+ })
482
+ pushUniqueEdge(edges, edgeIds, {
483
+ id: edgeId('contains', organizationNode.id, id),
484
+ kind: 'contains',
485
+ sourceId: organizationNode.id,
486
+ targetId: id
487
+ })
488
+ }
489
+
490
+ for (const action of Object.values(organizationModel.actions).sort((a, b) => a.id.localeCompare(b.id))) {
491
+ const id = nodeId('action', action.id)
492
+ pushUniqueNode(nodes, nodeIds, {
493
+ id,
494
+ kind: 'action',
495
+ label: action.label,
496
+ sourceId: action.id,
497
+ description: action.description
498
+ })
499
+ pushUniqueEdge(edges, edgeIds, {
500
+ id: edgeId('contains', organizationNode.id, id),
501
+ kind: 'contains',
502
+ sourceId: organizationNode.id,
503
+ targetId: id
504
+ })
505
+ if (action.resourceId !== undefined) {
506
+ const resourceNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, action.resourceId)
507
+ pushUniqueEdge(edges, edgeIds, {
508
+ id: edgeId('maps_to', id, resourceNode.id),
509
+ kind: 'maps_to',
510
+ sourceId: id,
511
+ targetId: resourceNode.id
512
+ })
513
+ }
514
+
515
+ for (const entityId of action.affects ?? []) {
516
+ pushUniqueEdge(edges, edgeIds, {
517
+ id: edgeId('affects', id, nodeId('entity', entityId)),
518
+ kind: 'affects',
519
+ sourceId: id,
520
+ targetId: nodeId('entity', entityId)
521
+ })
522
+ }
523
+
524
+ for (const invocation of action.invocations) {
525
+ const key = invocationSignature(invocation)
526
+ const existing = actionIdsByInvocation.get(key)
527
+ if (existing) {
528
+ existing.push(id)
529
+ } else {
530
+ actionIdsByInvocation.set(key, [id])
531
+ }
532
+ }
533
+ }
534
+
535
+ for (const entity of Object.values(organizationModel.entities).sort(
536
+ (a, b) => a.order - b.order || a.id.localeCompare(b.id)
537
+ )) {
538
+ const id = nodeId('entity', entity.id)
539
+ pushUniqueNode(nodes, nodeIds, {
540
+ id,
541
+ kind: 'entity',
542
+ label: entity.label,
543
+ sourceId: entity.id,
544
+ description: entity.description
545
+ })
546
+ pushUniqueEdge(edges, edgeIds, {
547
+ id: edgeId('contains', systemNodeId(entity.ownedBySystemId), id, 'system-entity'),
548
+ kind: 'contains',
549
+ sourceId: systemNodeId(entity.ownedBySystemId),
550
+ targetId: id
551
+ })
552
+
553
+ for (const [linkIndex, link] of (entity.links ?? []).entries()) {
554
+ pushUniqueEdge(edges, edgeIds, {
555
+ id: edgeId('links', id, nodeId('entity', link.toEntity), `${link.kind}-${linkIndex}`),
556
+ kind: 'links',
557
+ sourceId: id,
558
+ targetId: nodeId('entity', link.toEntity),
559
+ label: link.label ?? link.kind
560
+ })
561
+ }
562
+
563
+ if (entity.stateCatalogId !== undefined) {
564
+ const stateEvents: EventDescriptor[] = []
565
+
566
+ // Phase 4: model.statuses removed (D1). Compatibility status data can
567
+ // still project from System.content; primary status-flow authoring now
568
+ // belongs in System.ontology catalogTypes.
569
+
570
+ if (entity.stateCatalogId === 'crm.pipeline') {
571
+ // Phase 4: model.sales removed (D8). Read pipelines via migration helper.
572
+ for (const { pipeline } of getAllPipelines(organizationModel)
573
+ .filter(({ pipeline: p }) => p.entityId === entity.id)
574
+ .sort((a, b) => a.pipeline.id.localeCompare(b.pipeline.id))) {
575
+ for (const stage of [...pipeline.stages].sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))) {
576
+ stateEvents.push(buildEntityEventDescriptor(entity, stage.id, stage.label))
577
+ }
578
+ }
579
+ }
580
+
581
+ if (entity.stateCatalogId === 'delivery.task') {
582
+ // Phase 4: model.projects removed (D8). Read statuses via migration helper.
583
+ for (const status of getAllProjectStatuses(organizationModel, 'task').sort(
584
+ (a, b) => a.order - b.order || a.id.localeCompare(b.id)
585
+ )) {
586
+ stateEvents.push(buildEntityEventDescriptor(entity, status.id, status.label))
587
+ }
588
+ }
589
+
590
+ if (entity.stateCatalogId === 'lead-gen.company' || entity.stateCatalogId === 'lead-gen.contact') {
591
+ const leadGenEntity = entity.stateCatalogId === 'lead-gen.company' ? 'company' : 'contact'
592
+ for (const stage of Object.values(LEAD_GEN_STAGE_CATALOG).sort(
593
+ (a, b) => a.order - b.order || a.key.localeCompare(b.key)
594
+ )) {
595
+ if (stage.entity !== leadGenEntity && !(stage.additionalEntities ?? []).includes(leadGenEntity)) continue
596
+ stateEvents.push(buildEntityEventDescriptor(entity, stage.key, stage.label))
597
+ }
598
+ }
599
+
600
+ for (const event of stateEvents) {
601
+ pushEventProjection(
602
+ nodes,
603
+ nodeIds,
604
+ edges,
605
+ edgeIds,
606
+ projectedEventNodeIdsByEventId,
607
+ event,
608
+ id,
609
+ 'originates_from'
610
+ )
611
+ }
612
+ }
613
+ }
614
+
615
+ for (const { path, system } of systemsWithPaths.sort((a, b) => a.path.localeCompare(b.path))) {
616
+ for (const actionRef of system.actions ?? []) {
617
+ pushUniqueEdge(edges, edgeIds, {
618
+ id: edgeId('uses', nodeId('system', path), nodeId('action', actionRef.actionId), actionRef.intent),
619
+ kind: 'uses',
620
+ sourceId: nodeId('system', path),
621
+ targetId: nodeId('action', actionRef.actionId),
622
+ label: actionRef.intent
623
+ })
624
+ }
625
+ }
626
+
627
+ // Pre-compute the set of valid system paths so the resource loop below can
628
+ // skip dangling-path edge emission in O(1) per resource. Belt-and-suspenders
629
+ // against the Wave 2 superRefine; reachable only for partial-OM fixture cases.
630
+ const validSystemPaths = new Set([...validSystemRefs])
631
+
632
+ for (const resource of Object.values(organizationModel.resources).sort((a, b) => a.id.localeCompare(b.id))) {
633
+ const resourceNode = upsertResourceNode(nodes, nodeIds, resourceNodesById, {
634
+ id: nodeId('resource', resource.id),
635
+ kind: 'resource',
636
+ label: resource.id,
637
+ sourceId: resource.id,
638
+ resourceType: normalizeOrganizationModelResourceType(resource.kind)
639
+ })
640
+
641
+ // Skip the contains edge if the systemPath doesn't resolve to a real system.
642
+ // In a fully-validated model this never fires; it guards partial-OM fixtures.
643
+ if (validSystemPaths.has(resource.systemPath)) {
299
644
  pushUniqueEdge(edges, edgeIds, {
300
- id: edgeId('contains', organizationNode.id, targetNode.id),
645
+ id: edgeId('contains', systemNodeId(resource.systemPath), resourceNode.id, 'system-resource'),
301
646
  kind: 'contains',
302
- sourceId: organizationNode.id,
303
- targetId: targetNode.id
304
- })
305
-
306
- pushUniqueEdge(edges, edgeIds, {
307
- id: edgeId('references', sourceNode.id, targetNode.id, relationship.relationship),
308
- kind: 'references',
309
- sourceId: sourceNode.id,
310
- targetId: targetNode.id,
311
- label: relationship.relationship,
312
- relationshipType: relationship.relationship
647
+ sourceId: systemNodeId(resource.systemPath),
648
+ targetId: resourceNode.id
313
649
  })
314
650
  }
315
- }
316
651
 
317
- const graph: OrganizationGraph = {
318
- version: 1,
319
- organizationModelVersion: organizationModel.version,
320
- nodes,
321
- edges
322
- }
652
+ pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'implements', resource.ontology?.implements)
653
+ pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'reads', resource.ontology?.reads)
654
+ pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'writes', resource.ontology?.writes)
655
+ pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'uses_catalog', resource.ontology?.usesCatalogs)
656
+ pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'emits', resource.ontology?.emits)
323
657
 
324
- return OrganizationGraphSchema.parse(graph)
325
- }
658
+ if (resource.kind === 'workflow' || resource.kind === 'agent') {
659
+ for (const emission of resource.emits ?? []) {
660
+ pushEventProjection(
661
+ nodes,
662
+ nodeIds,
663
+ edges,
664
+ edgeIds,
665
+ projectedEventNodeIdsByEventId,
666
+ buildResourceEventDescriptor(resource.id, emission),
667
+ resourceNode.id,
668
+ 'emits'
669
+ )
670
+ }
671
+ }
672
+
673
+ if (resource.kind === 'agent') {
674
+ for (const [index, invocation] of resource.invocations.entries()) {
675
+ const label = invocationLabel(invocation)
676
+ if (invocation.kind === 'script-execution') {
677
+ const targetNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, invocation.resourceId)
678
+ pushUniqueEdge(edges, edgeIds, {
679
+ id: edgeId('uses', resourceNode.id, targetNode.id, `agent-invocation-${index}`),
680
+ kind: 'uses',
681
+ sourceId: resourceNode.id,
682
+ targetId: targetNode.id,
683
+ label
684
+ })
685
+ continue
686
+ }
687
+
688
+ for (const actionNodeId of actionIdsByInvocation.get(invocationSignature(invocation)) ?? []) {
689
+ pushUniqueEdge(edges, edgeIds, {
690
+ id: edgeId('references', resourceNode.id, actionNodeId, `agent-invocation-${index}`),
691
+ kind: 'references',
692
+ sourceId: resourceNode.id,
693
+ targetId: actionNodeId,
694
+ label
695
+ })
696
+ }
697
+ }
698
+ }
699
+ }
700
+
701
+ for (const policy of Object.values(organizationModel.policies).sort(
702
+ (a, b) => a.order - b.order || a.id.localeCompare(b.id)
703
+ )) {
704
+ const id = nodeId('policy', policy.id)
705
+ pushUniqueNode(nodes, nodeIds, {
706
+ id,
707
+ kind: 'policy',
708
+ label: policy.label,
709
+ sourceId: policy.id,
710
+ description: policy.description
711
+ })
712
+ pushUniqueEdge(edges, edgeIds, {
713
+ id: edgeId('contains', organizationNode.id, id),
714
+ kind: 'contains',
715
+ sourceId: organizationNode.id,
716
+ targetId: id
717
+ })
718
+
719
+ for (const systemId of policy.appliesTo.systemIds) {
720
+ pushUniqueEdge(edges, edgeIds, {
721
+ id: edgeId('applies_to', id, systemNodeId(systemId), 'system'),
722
+ kind: 'applies_to',
723
+ sourceId: id,
724
+ targetId: systemNodeId(systemId)
725
+ })
726
+ }
727
+ for (const actionId of policy.appliesTo.actionIds) {
728
+ pushUniqueEdge(edges, edgeIds, {
729
+ id: edgeId('applies_to', id, nodeId('action', actionId), 'action'),
730
+ kind: 'applies_to',
731
+ sourceId: id,
732
+ targetId: nodeId('action', actionId)
733
+ })
734
+ }
735
+ for (const resourceId of policy.appliesTo.resourceIds) {
736
+ const resourceNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, resourceId)
737
+ pushUniqueEdge(edges, edgeIds, {
738
+ id: edgeId('applies_to', id, resourceNode.id, 'resource'),
739
+ kind: 'applies_to',
740
+ sourceId: id,
741
+ targetId: resourceNode.id
742
+ })
743
+ }
744
+ for (const roleId of policy.appliesTo.roleIds) {
745
+ pushUniqueEdge(edges, edgeIds, {
746
+ id: edgeId('applies_to', id, nodeId('role', roleId), 'role'),
747
+ kind: 'applies_to',
748
+ sourceId: id,
749
+ targetId: nodeId('role', roleId)
750
+ })
751
+ }
752
+
753
+ if (policy.trigger.kind === 'event') {
754
+ const eventNode = projectedEventNodeIdsByEventId.get(policy.trigger.eventId)
755
+ if (eventNode !== undefined) {
756
+ pushUniqueEdge(edges, edgeIds, {
757
+ id: edgeId('triggers', eventNode, id),
758
+ kind: 'triggers',
759
+ sourceId: eventNode,
760
+ targetId: id
761
+ })
762
+ }
763
+ } else if (policy.trigger.kind === 'action-invocation') {
764
+ pushUniqueEdge(edges, edgeIds, {
765
+ id: edgeId('triggers', nodeId('action', policy.trigger.actionId), id),
766
+ kind: 'triggers',
767
+ sourceId: nodeId('action', policy.trigger.actionId),
768
+ targetId: id
769
+ })
770
+ }
771
+
772
+ for (const [effectIndex, effect] of policy.actions.entries()) {
773
+ if (effect.kind === 'invoke-action') {
774
+ pushUniqueEdge(edges, edgeIds, {
775
+ id: edgeId('effects', id, nodeId('action', effect.actionId), `invoke-action-${effectIndex}`),
776
+ kind: 'effects',
777
+ sourceId: id,
778
+ targetId: nodeId('action', effect.actionId),
779
+ label: 'invoke action'
780
+ })
781
+ }
782
+ if ((effect.kind === 'notify-role' || effect.kind === 'require-approval') && effect.roleId !== undefined) {
783
+ pushUniqueEdge(edges, edgeIds, {
784
+ id: edgeId('effects', id, nodeId('role', effect.roleId), `${effect.kind}-${effectIndex}`),
785
+ kind: 'effects',
786
+ sourceId: id,
787
+ targetId: nodeId('role', effect.roleId),
788
+ label: effect.kind
789
+ })
790
+ }
791
+ }
792
+ }
793
+
794
+ for (const segment of Object.values(organizationModel.customers).sort(
795
+ (a, b) => a.order - b.order || a.id.localeCompare(b.id)
796
+ )) {
797
+ const id = nodeId('customer-segment', segment.id)
798
+ pushUniqueNode(nodes, nodeIds, {
799
+ id,
800
+ kind: 'customer-segment',
801
+ label: segment.name,
802
+ sourceId: segment.id,
803
+ description: segment.description || undefined
804
+ })
805
+ pushUniqueEdge(edges, edgeIds, {
806
+ id: edgeId('contains', organizationNode.id, id),
807
+ kind: 'contains',
808
+ sourceId: organizationNode.id,
809
+ targetId: id
810
+ })
811
+ }
812
+
813
+ for (const product of Object.values(organizationModel.offerings).sort(
814
+ (a, b) => a.order - b.order || a.id.localeCompare(b.id)
815
+ )) {
816
+ const id = nodeId('offering', product.id)
817
+ pushUniqueNode(nodes, nodeIds, {
818
+ id,
819
+ kind: 'offering',
820
+ label: product.name,
821
+ sourceId: product.id,
822
+ description: product.description || undefined
823
+ })
824
+ pushUniqueEdge(edges, edgeIds, {
825
+ id: edgeId('contains', organizationNode.id, id),
826
+ kind: 'contains',
827
+ sourceId: organizationNode.id,
828
+ targetId: id
829
+ })
830
+ for (const segmentId of product.targetSegmentIds) {
831
+ pushUniqueEdge(edges, edgeIds, {
832
+ id: edgeId('applies_to', id, nodeId('customer-segment', segmentId), 'targets-segment'),
833
+ kind: 'applies_to',
834
+ sourceId: id,
835
+ targetId: nodeId('customer-segment', segmentId),
836
+ label: 'targets'
837
+ })
838
+ }
839
+ }
840
+
841
+ for (const objective of Object.values(organizationModel.goals).sort(
842
+ (a, b) => a.order - b.order || a.id.localeCompare(b.id)
843
+ )) {
844
+ const id = nodeId('goal', objective.id)
845
+ pushUniqueNode(nodes, nodeIds, {
846
+ id,
847
+ kind: 'goal',
848
+ label: objective.description,
849
+ sourceId: objective.id
850
+ })
851
+ pushUniqueEdge(edges, edgeIds, {
852
+ id: edgeId('contains', organizationNode.id, id),
853
+ kind: 'contains',
854
+ sourceId: organizationNode.id,
855
+ targetId: id
856
+ })
857
+ }
858
+
859
+ const sidebarGroups: Array<{
860
+ id: string
861
+ node: Extract<OrganizationModelSidebarNode, { type: 'group' }>
862
+ parentGroupId?: string
863
+ }> = []
864
+ const sidebarSurfaces: Array<{
865
+ id: string
866
+ node: Extract<OrganizationModelSidebarNode, { type: 'surface' }>
867
+ parentGroupId?: string
868
+ }> = []
869
+ collectSidebarGraphNodes(organizationModel.navigation.sidebar.primary, sidebarGroups, sidebarSurfaces)
870
+ collectSidebarGraphNodes(organizationModel.navigation.sidebar.bottom, sidebarGroups, sidebarSurfaces)
871
+
872
+ for (const { id: surfaceSourceId, node: surface, parentGroupId } of sidebarSurfaces) {
873
+ const id = nodeId('surface', surfaceSourceId)
874
+ pushUniqueNode(nodes, nodeIds, {
875
+ id,
876
+ kind: 'surface',
877
+ label: surface.label,
878
+ sourceId: surfaceSourceId,
879
+ description: surface.description,
880
+ icon: surface.icon,
881
+ enabled: true
882
+ })
883
+ pushUniqueEdge(edges, edgeIds, {
884
+ id: edgeId('contains', parentGroupId ? nodeId('navigation-group', parentGroupId) : organizationNode.id, id),
885
+ kind: 'contains',
886
+ sourceId: parentGroupId ? nodeId('navigation-group', parentGroupId) : organizationNode.id,
887
+ targetId: id
888
+ })
889
+ for (const systemId of surface.targets?.systems ?? []) {
890
+ if (!validSystemRefs.has(systemId)) continue
891
+
892
+ pushUniqueEdge(edges, edgeIds, {
893
+ id: edgeId('applies_to', id, systemNodeId(systemId), 'surface-system'),
894
+ kind: 'applies_to',
895
+ sourceId: id,
896
+ targetId: systemNodeId(systemId)
897
+ })
898
+ }
899
+ }
900
+
901
+ for (const { id: groupSourceId, node: group, parentGroupId } of sidebarGroups) {
902
+ const id = nodeId('navigation-group', groupSourceId)
903
+ pushUniqueNode(nodes, nodeIds, {
904
+ id,
905
+ kind: 'navigation-group',
906
+ label: group.label,
907
+ sourceId: groupSourceId
908
+ })
909
+ pushUniqueEdge(edges, edgeIds, {
910
+ id: edgeId('contains', parentGroupId ? nodeId('navigation-group', parentGroupId) : organizationNode.id, id),
911
+ kind: 'contains',
912
+ sourceId: parentGroupId ? nodeId('navigation-group', parentGroupId) : organizationNode.id,
913
+ targetId: id
914
+ })
915
+ }
916
+
917
+ // ---------------------------------------------------------------------------
918
+ // Compatibility bridge: content-node graph projection
919
+ // ---------------------------------------------------------------------------
920
+ // Keep bridge-era System.content addressable as read-only content-node:* graph
921
+ // IDs for old UI and knowledge consumers. New semantic graph consumers should
922
+ // prefer ontology-native graph nodes emitted from System.ontology.
923
+ //
924
+ // For every system in the model tree, emit a 'content-node' graph node for
925
+ // each entry in system.content[*]. Edges:
926
+ // - contains: system spine node → content-node
927
+ // - contains: parent content-node → child content-node (via parentContentId)
928
+ // - references: pipeline content-node → entity node (when data.entityId set)
929
+ // ---------------------------------------------------------------------------
930
+ for (const { path, system } of listAllSystems(organizationModel).sort((a, b) => a.path.localeCompare(b.path))) {
931
+ const contentMap = system.content ?? {}
932
+ const systemSpineId = nodeId('system', path)
933
+
934
+ for (const [localId, contentNode] of Object.entries(contentMap).sort(([a], [b]) => a.localeCompare(b))) {
935
+ const contentNodeGraphId = `content-node:${path}:${localId}`
936
+
937
+ pushUniqueNode(nodes, nodeIds, {
938
+ id: contentNodeGraphId,
939
+ kind: 'content-node',
940
+ label: contentNode.label,
941
+ sourceId: `${path}:${localId}`,
942
+ description: contentNode.description
943
+ // Spread contentKind and contentType into attributes; the node schema
944
+ // does not have custom attribute slots, so we encode them in the label
945
+ // suffix for now. The actual kind/type are recoverable from sourceId +
946
+ // the registry lookup by consumers.
947
+ })
948
+
949
+ // contains: system spine → content-node
950
+ pushUniqueEdge(edges, edgeIds, {
951
+ id: edgeId('contains', systemSpineId, contentNodeGraphId, 'system-content'),
952
+ kind: 'contains',
953
+ sourceId: systemSpineId,
954
+ targetId: contentNodeGraphId
955
+ })
956
+
957
+ // contains: parent content-node → child content-node (parentContentId chain)
958
+ if (contentNode.parentContentId) {
959
+ const parentContentNodeGraphId = `content-node:${path}:${contentNode.parentContentId}`
960
+ pushUniqueEdge(edges, edgeIds, {
961
+ id: edgeId('contains', parentContentNodeGraphId, contentNodeGraphId, 'content-parent'),
962
+ kind: 'contains',
963
+ sourceId: parentContentNodeGraphId,
964
+ targetId: contentNodeGraphId
965
+ })
966
+ }
967
+
968
+ // references: pipeline -> entity (data.entityId ref-field annotation)
969
+ // Bridge schema:pipeline payloadSchema carries `.meta({ ref: 'entity' })` on entityId.
970
+ // Emit a 'references' edge from the pipeline content-node to the entity node.
971
+ if (
972
+ contentNode.kind === 'schema' &&
973
+ contentNode.type === 'pipeline' &&
974
+ contentNode.data &&
975
+ typeof contentNode.data['entityId'] === 'string'
976
+ ) {
977
+ const targetEntityId = nodeId('entity', contentNode.data['entityId'])
978
+ pushUniqueEdge(edges, edgeIds, {
979
+ id: edgeId('references', contentNodeGraphId, targetEntityId, 'pipeline-entity'),
980
+ kind: 'references',
981
+ sourceId: contentNodeGraphId,
982
+ targetId: targetEntityId,
983
+ label: 'applies to entity'
984
+ })
985
+ }
986
+ }
987
+ }
988
+
989
+ // Phase 4: prospecting domain removed; read templates via migration helper.
990
+ // Steps are typed as BuildTemplate['steps'] which carries ProspectingBuildTemplateStepSchema fields.
991
+ type TemplateStep = { id: string; stageKey: string; actionKey: string; dependsOn?: string[] }
992
+ for (const template of getAllBuildTemplates(organizationModel).sort((a, b) => a.id.localeCompare(b.id))) {
993
+ const steps = template.steps as unknown as TemplateStep[]
994
+ const stepById = new Map(steps.map((s) => [s.id, s]))
995
+ for (const step of [...steps].sort((a, b) => a.id.localeCompare(b.id))) {
996
+ const stageNodeId = nodeId('stage', step.stageKey)
997
+ const actionNodeId = nodeId('action', step.actionKey)
998
+ pushUniqueEdge(edges, edgeIds, {
999
+ id: edgeId('uses', stageNodeId, actionNodeId, step.id),
1000
+ kind: 'uses',
1001
+ sourceId: stageNodeId,
1002
+ targetId: actionNodeId
1003
+ })
1004
+ for (const depId of step.dependsOn ?? []) {
1005
+ const depStep = stepById.get(depId)
1006
+ if (depStep) {
1007
+ const depStageNodeId = nodeId('stage', depStep.stageKey)
1008
+ pushUniqueEdge(edges, edgeIds, {
1009
+ id: edgeId('references', stageNodeId, depStageNodeId, step.id),
1010
+ kind: 'references',
1011
+ sourceId: stageNodeId,
1012
+ targetId: depStageNodeId
1013
+ })
1014
+ }
1015
+ }
1016
+ }
1017
+ }
1018
+
1019
+ if (commandViewData) {
1020
+ const commandViewResources = collectCommandViewResources(commandViewData).sort((a, b) =>
1021
+ a.resourceId.localeCompare(b.resourceId)
1022
+ )
1023
+
1024
+ for (const resource of commandViewResources) {
1025
+ const id = nodeId('resource', resource.resourceId)
1026
+ const resourceNode = upsertResourceNode(nodes, nodeIds, resourceNodesById, {
1027
+ id,
1028
+ kind: 'resource',
1029
+ label: resource.name,
1030
+ sourceId: resource.resourceId,
1031
+ description: resource.description,
1032
+ resourceType: normalizeCommandViewResourceType(resource.type)
1033
+ })
1034
+
1035
+ if (!organizationModelResourceIds.has(resource.resourceId)) {
1036
+ pushUniqueEdge(edges, edgeIds, {
1037
+ id: edgeId('contains', organizationNode.id, resourceNode.id),
1038
+ kind: 'contains',
1039
+ sourceId: organizationNode.id,
1040
+ targetId: resourceNode.id
1041
+ })
1042
+ }
1043
+ }
1044
+
1045
+ for (const relationship of [...commandViewData.edges].sort((a, b) => a.id.localeCompare(b.id))) {
1046
+ const sourceNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, relationship.source)
1047
+ const targetNode = ensureResourceNode(nodes, nodeIds, resourceNodesById, relationship.target)
1048
+
1049
+ pushUniqueEdge(edges, edgeIds, {
1050
+ id: edgeId('contains', organizationNode.id, sourceNode.id),
1051
+ kind: 'contains',
1052
+ sourceId: organizationNode.id,
1053
+ targetId: sourceNode.id
1054
+ })
1055
+ pushUniqueEdge(edges, edgeIds, {
1056
+ id: edgeId('contains', organizationNode.id, targetNode.id),
1057
+ kind: 'contains',
1058
+ sourceId: organizationNode.id,
1059
+ targetId: targetNode.id
1060
+ })
1061
+
1062
+ pushUniqueEdge(edges, edgeIds, {
1063
+ id: edgeId('references', sourceNode.id, targetNode.id, relationship.relationship),
1064
+ kind: 'references',
1065
+ sourceId: sourceNode.id,
1066
+ targetId: targetNode.id,
1067
+ label: relationship.relationship,
1068
+ relationshipType: relationship.relationship
1069
+ })
1070
+ }
1071
+ }
1072
+
1073
+ const graph: OrganizationGraph = {
1074
+ version: 1,
1075
+ organizationModelVersion: organizationModel.version,
1076
+ nodes,
1077
+ edges
1078
+ }
1079
+
1080
+ return OrganizationGraphSchema.parse(graph)
1081
+ }