@elevasis/core 0.25.0 → 0.26.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 (66) hide show
  1. package/dist/index.d.ts +166 -85
  2. package/dist/index.js +146 -1346
  3. package/dist/knowledge/index.d.ts +27 -38
  4. package/dist/knowledge/index.js +1 -1
  5. package/dist/organization-model/index.d.ts +166 -85
  6. package/dist/organization-model/index.js +146 -1346
  7. package/dist/test-utils/index.d.ts +23 -31
  8. package/dist/test-utils/index.js +75 -1238
  9. package/package.json +1 -1
  10. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +14 -2
  11. package/src/business/acquisition/api-schemas.test.ts +70 -77
  12. package/src/business/acquisition/api-schemas.ts +21 -42
  13. package/src/business/acquisition/derive-actions.test.ts +11 -21
  14. package/src/business/acquisition/derive-actions.ts +61 -14
  15. package/src/business/acquisition/ontology-validation.ts +4 -4
  16. package/src/business/acquisition/types.ts +7 -8
  17. package/src/knowledge/queries.ts +0 -1
  18. package/src/organization-model/__tests__/content-kinds-registry.test.ts +35 -210
  19. package/src/organization-model/__tests__/defaults.test.ts +4 -4
  20. package/src/organization-model/__tests__/domains/actions.test.ts +12 -36
  21. package/src/organization-model/__tests__/domains/offerings.test.ts +13 -6
  22. package/src/organization-model/__tests__/domains/resources.test.ts +497 -350
  23. package/src/organization-model/__tests__/domains/systems.test.ts +6 -7
  24. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +68 -80
  25. package/src/organization-model/__tests__/foundation.test.ts +81 -14
  26. package/src/organization-model/__tests__/graph.test.ts +662 -694
  27. package/src/organization-model/__tests__/knowledge.test.ts +31 -17
  28. package/src/organization-model/__tests__/lookup-helpers.test.ts +128 -438
  29. package/src/organization-model/__tests__/migration-helpers.test.ts +362 -591
  30. package/src/organization-model/__tests__/prospecting-ssot.test.ts +68 -103
  31. package/src/organization-model/__tests__/published-zero-leak.test.ts +17 -0
  32. package/src/organization-model/__tests__/recursive-system-schema.test.ts +159 -532
  33. package/src/organization-model/__tests__/resolve.test.ts +79 -42
  34. package/src/organization-model/__tests__/schema.test.ts +65 -56
  35. package/src/organization-model/catalogs/lead-gen.ts +0 -103
  36. package/src/organization-model/defaults.ts +17 -702
  37. package/src/organization-model/domains/actions.ts +116 -333
  38. package/src/organization-model/domains/knowledge.ts +15 -7
  39. package/src/organization-model/domains/projects.ts +4 -4
  40. package/src/organization-model/domains/prospecting.ts +405 -395
  41. package/src/organization-model/domains/resources.ts +206 -135
  42. package/src/organization-model/domains/sales.ts +5 -5
  43. package/src/organization-model/domains/systems.ts +8 -23
  44. package/src/organization-model/graph/build.ts +223 -294
  45. package/src/organization-model/graph/schema.ts +2 -3
  46. package/src/organization-model/graph/types.ts +12 -14
  47. package/src/organization-model/helpers.ts +130 -218
  48. package/src/organization-model/index.ts +104 -124
  49. package/src/organization-model/migration-helpers.ts +211 -249
  50. package/src/organization-model/ontology.ts +0 -60
  51. package/src/organization-model/organization-graph.mdx +4 -5
  52. package/src/organization-model/organization-model.mdx +1 -1
  53. package/src/organization-model/published.ts +236 -226
  54. package/src/organization-model/resolve.ts +4 -5
  55. package/src/organization-model/schema.ts +610 -704
  56. package/src/organization-model/types.ts +167 -161
  57. package/src/platform/registry/__tests__/validation.test.ts +23 -0
  58. package/src/platform/registry/validation.ts +13 -2
  59. package/src/reference/_generated/contracts.md +14 -2
  60. package/src/organization-model/content-kinds/config.ts +0 -36
  61. package/src/organization-model/content-kinds/index.ts +0 -78
  62. package/src/organization-model/content-kinds/pipeline.ts +0 -68
  63. package/src/organization-model/content-kinds/registry.ts +0 -44
  64. package/src/organization-model/content-kinds/status.ts +0 -71
  65. package/src/organization-model/content-kinds/template.ts +0 -83
  66. package/src/organization-model/content-kinds/types.ts +0 -117
@@ -2,36 +2,36 @@ import { BuildOrganizationGraphInputSchema, OrganizationGraphSchema } from './sc
2
2
  import type { CommandViewData } from '../../platform/registry/command-view'
3
3
  import type {
4
4
  BuildOrganizationGraphInput,
5
- OrganizationGraph,
6
- OrganizationGraphEdge,
7
- OrganizationGraphEdgeKind,
8
- OrganizationGraphNode,
9
- OrganizationGraphNodeKind
10
- } from './types'
11
- import type { ActionInvocation } from '../domains/actions'
12
- import type { Entity } from '../domains/entities'
13
- import type { ResourceEntry } from '../domains/resources'
14
- import type { OmTopologyNodeRef } from '../domains/topology'
15
- import type { EventDescriptor, OrganizationModelSidebarNode } from '../types'
5
+ OrganizationGraph,
6
+ OrganizationGraphEdge,
7
+ OrganizationGraphEdgeKind,
8
+ OrganizationGraphNode,
9
+ OrganizationGraphNodeKind
10
+ } from './types'
11
+ import type { ActionInvocation } from '../domains/actions'
12
+ import type { Entity } from '../domains/entities'
13
+ import type { ResourceEntry } from '../domains/resources'
14
+ import type { OmTopologyNodeRef } from '../domains/topology'
15
+ import type { EventDescriptor, OrganizationModelSidebarNode } from '../types'
16
16
  import {
17
17
  getAllPipelines,
18
18
  getAllBuildTemplates,
19
+ getLeadGenStageCatalog,
19
20
  getAllProjectStatuses,
20
21
  getAllProspectingStages
21
22
  } from '../migration-helpers'
22
- import { LEAD_GEN_STAGE_CATALOG } from '../catalogs/lead-gen'
23
- import { listAllSystems } from '../helpers'
24
- import {
25
- compileOrganizationOntology,
26
- listResolvedOntologyRecords,
27
- ontologyGraphNodeId,
28
- parseOntologyId,
29
- type OntologyActionType,
30
- type OntologyCatalogType,
31
- type OntologyGroup,
32
- type OntologyId,
33
- type OntologyLinkType
34
- } from '../ontology'
23
+ import { listAllSystems } from '../helpers'
24
+ import {
25
+ compileOrganizationOntology,
26
+ listResolvedOntologyRecords,
27
+ ontologyGraphNodeId,
28
+ parseOntologyId,
29
+ type OntologyActionType,
30
+ type OntologyCatalogType,
31
+ type OntologyGroup,
32
+ type OntologyId,
33
+ type OntologyLinkType
34
+ } from '../ontology'
35
35
 
36
36
  type EventEmissionDescriptor = NonNullable<Extract<ResourceEntry, { kind: 'workflow' }>['emits']>[number]
37
37
 
@@ -187,31 +187,31 @@ function invocationLabel(invocation: ActionInvocation): string {
187
187
  }
188
188
  }
189
189
 
190
- function eventNodeId(eventId: string): string {
191
- return nodeId('event', eventId)
192
- }
193
-
194
- function ontologyLabel(id: OntologyId, label: string | undefined): string {
195
- return label ?? parseOntologyId(id).localId
196
- }
197
-
198
- function pushOntologyBindingEdges(
199
- edges: OrganizationGraphEdge[],
200
- edgeIds: Set<string>,
201
- resourceNodeId: string,
202
- kind: OrganizationGraphEdgeKind,
203
- ids: OntologyId[] | undefined
204
- ): void {
205
- ids?.forEach((ontologyId) => {
206
- const targetId = ontologyGraphNodeId(ontologyId)
207
- pushUniqueEdge(edges, edgeIds, {
208
- id: edgeId(kind, resourceNodeId, targetId),
209
- kind,
210
- sourceId: resourceNodeId,
211
- targetId
212
- })
213
- })
214
- }
190
+ function eventNodeId(eventId: string): string {
191
+ return nodeId('event', eventId)
192
+ }
193
+
194
+ function ontologyLabel(id: OntologyId, label: string | undefined): string {
195
+ return label ?? parseOntologyId(id).localId
196
+ }
197
+
198
+ function pushOntologyBindingEdges(
199
+ edges: OrganizationGraphEdge[],
200
+ edgeIds: Set<string>,
201
+ resourceNodeId: string,
202
+ kind: OrganizationGraphEdgeKind,
203
+ ids: OntologyId[] | undefined
204
+ ): void {
205
+ ids?.forEach((ontologyId) => {
206
+ const targetId = ontologyGraphNodeId(ontologyId)
207
+ pushUniqueEdge(edges, edgeIds, {
208
+ id: edgeId(kind, resourceNodeId, targetId),
209
+ kind,
210
+ sourceId: resourceNodeId,
211
+ targetId
212
+ })
213
+ })
214
+ }
215
215
 
216
216
  function buildResourceEventDescriptor(resourceId: string, emission: EventEmissionDescriptor): EventDescriptor {
217
217
  return {
@@ -270,10 +270,10 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
270
270
  const nodeIds = new Set<string>()
271
271
  const edgeIds = new Set<string>()
272
272
  const resourceNodesById = new Map<string, OrganizationGraphNode>()
273
- const organizationModelResourceIds = new Set(Object.keys(organizationModel.resources))
274
- const actionIdsByInvocation = new Map<string, string[]>()
275
- const projectedEventNodeIdsByEventId = new Map<string, string>()
276
- const ontologyCompilation = compileOrganizationOntology(organizationModel)
273
+ const organizationModelResourceIds = new Set(Object.keys(organizationModel.resources))
274
+ const actionIdsByInvocation = new Map<string, string[]>()
275
+ const projectedEventNodeIdsByEventId = new Map<string, string>()
276
+ const ontologyCompilation = compileOrganizationOntology(organizationModel)
277
277
 
278
278
  const organizationNode: OrganizationGraphNode = {
279
279
  id: nodeId('organization'),
@@ -287,42 +287,42 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
287
287
  systemPathByRef.set(path, path)
288
288
  systemPathByRef.set(system.id, path)
289
289
  }
290
- const validSystemRefs = new Set(systemPathByRef.keys())
291
- const systemNodeId = (systemRef: string) => nodeId('system', systemPathByRef.get(systemRef) ?? systemRef)
292
-
293
- function topologyNodeId(ref: OmTopologyNodeRef): string {
294
- if (ref.kind === 'system') return systemNodeId(ref.id)
295
- if (ref.kind === 'resource') return nodeId('resource', ref.id)
296
- if (ref.kind === 'ontology') return ontologyGraphNodeId(ref.id)
297
- if (ref.kind === 'policy') return nodeId('policy', ref.id)
298
- if (ref.kind === 'role') return nodeId('role', ref.id)
299
- return nodeId('resource', ref.id)
300
- }
301
-
302
- function ensureTopologyNode(ref: OmTopologyNodeRef): string {
303
- const id = topologyNodeId(ref)
304
- if (nodeIds.has(id)) return id
305
-
306
- if (ref.kind === 'resource') {
307
- ensureResourceNode(nodes, nodeIds, resourceNodesById, ref.id)
308
- return id
309
- }
310
-
311
- if (ref.kind === 'trigger' || ref.kind === 'humanCheckpoint' || ref.kind === 'externalResource') {
312
- pushUniqueNode(nodes, nodeIds, {
313
- id,
314
- kind: 'resource',
315
- label: ref.id,
316
- sourceId: ref.id,
317
- resourceType:
318
- ref.kind === 'trigger' ? 'trigger' : ref.kind === 'humanCheckpoint' ? 'human_checkpoint' : 'external'
319
- })
320
- }
321
-
322
- return id
323
- }
324
-
325
- for (const { path, system } of systemsWithPaths.sort((a, b) => a.path.localeCompare(b.path))) {
290
+ const validSystemRefs = new Set(systemPathByRef.keys())
291
+ const systemNodeId = (systemRef: string) => nodeId('system', systemPathByRef.get(systemRef) ?? systemRef)
292
+
293
+ function topologyNodeId(ref: OmTopologyNodeRef): string {
294
+ if (ref.kind === 'system') return systemNodeId(ref.id)
295
+ if (ref.kind === 'resource') return nodeId('resource', ref.id)
296
+ if (ref.kind === 'ontology') return ontologyGraphNodeId(ref.id)
297
+ if (ref.kind === 'policy') return nodeId('policy', ref.id)
298
+ if (ref.kind === 'role') return nodeId('role', ref.id)
299
+ return nodeId('resource', ref.id)
300
+ }
301
+
302
+ function ensureTopologyNode(ref: OmTopologyNodeRef): string {
303
+ const id = topologyNodeId(ref)
304
+ if (nodeIds.has(id)) return id
305
+
306
+ if (ref.kind === 'resource') {
307
+ ensureResourceNode(nodes, nodeIds, resourceNodesById, ref.id)
308
+ return id
309
+ }
310
+
311
+ if (ref.kind === 'trigger' || ref.kind === 'humanCheckpoint' || ref.kind === 'externalResource') {
312
+ pushUniqueNode(nodes, nodeIds, {
313
+ id,
314
+ kind: 'resource',
315
+ label: ref.id,
316
+ sourceId: ref.id,
317
+ resourceType:
318
+ ref.kind === 'trigger' ? 'trigger' : ref.kind === 'humanCheckpoint' ? 'human_checkpoint' : 'external'
319
+ })
320
+ }
321
+
322
+ return id
323
+ }
324
+
325
+ for (const { path, system } of systemsWithPaths.sort((a, b) => a.path.localeCompare(b.path))) {
326
326
  const id = nodeId('system', path)
327
327
  pushUniqueNode(nodes, nodeIds, {
328
328
  id,
@@ -348,85 +348,85 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
348
348
  targetId: id
349
349
  })
350
350
  }
351
- }
352
-
353
- for (const { id: ontologyId, kind, record } of listResolvedOntologyRecords(ontologyCompilation.ontology).sort((a, b) =>
354
- a.id.localeCompare(b.id)
355
- )) {
356
- const id = ontologyGraphNodeId(ontologyId)
357
- const parsedId = parseOntologyId(ontologyId)
358
- pushUniqueNode(nodes, nodeIds, {
359
- id,
360
- kind: 'ontology',
361
- label: ontologyLabel(ontologyId, record.label),
362
- sourceId: ontologyId,
363
- description: record.description,
364
- ontologyKind: kind
365
- })
366
-
367
- const ownerSystemId = record.ownerSystemId ?? (parsedId.isGlobal ? undefined : parsedId.scope)
368
- if (ownerSystemId !== undefined && validSystemRefs.has(ownerSystemId)) {
369
- pushUniqueEdge(edges, edgeIds, {
370
- id: edgeId('contains', systemNodeId(ownerSystemId), id, `ontology-${kind}`),
371
- kind: 'contains',
372
- sourceId: systemNodeId(ownerSystemId),
373
- targetId: id
374
- })
375
- } else {
376
- pushUniqueEdge(edges, edgeIds, {
377
- id: edgeId('contains', organizationNode.id, id, `ontology-${kind}`),
378
- kind: 'contains',
379
- sourceId: organizationNode.id,
380
- targetId: id
381
- })
382
- }
383
-
384
- if (kind === 'link') {
385
- const link = record as OntologyLinkType
386
- pushUniqueEdge(edges, edgeIds, {
387
- id: edgeId('links', ontologyGraphNodeId(link.from), ontologyGraphNodeId(link.to), ontologyId),
388
- kind: 'links',
389
- sourceId: ontologyGraphNodeId(link.from),
390
- targetId: ontologyGraphNodeId(link.to),
391
- label: link.label ?? parsedId.localId
392
- })
393
- }
394
-
395
- if (kind === 'action') {
396
- const action = record as OntologyActionType
397
- for (const targetOntologyId of action.actsOn ?? []) {
398
- pushUniqueEdge(edges, edgeIds, {
399
- id: edgeId('affects', id, ontologyGraphNodeId(targetOntologyId), ontologyId),
400
- kind: 'affects',
401
- sourceId: id,
402
- targetId: ontologyGraphNodeId(targetOntologyId)
403
- })
404
- }
405
- }
406
-
407
- if (kind === 'catalog') {
408
- const catalog = record as OntologyCatalogType
409
- if (catalog.appliesTo === undefined) continue
410
- pushUniqueEdge(edges, edgeIds, {
411
- id: edgeId('applies_to', id, ontologyGraphNodeId(catalog.appliesTo), ontologyId),
412
- kind: 'applies_to',
413
- sourceId: id,
414
- targetId: ontologyGraphNodeId(catalog.appliesTo)
415
- })
416
- }
417
-
418
- if (kind === 'group') {
419
- const group = record as OntologyGroup
420
- for (const memberOntologyId of group.members ?? []) {
421
- pushUniqueEdge(edges, edgeIds, {
422
- id: edgeId('contains', id, ontologyGraphNodeId(memberOntologyId), ontologyId),
423
- kind: 'contains',
424
- sourceId: id,
425
- targetId: ontologyGraphNodeId(memberOntologyId)
426
- })
427
- }
428
- }
429
- }
351
+ }
352
+
353
+ for (const { id: ontologyId, kind, record } of listResolvedOntologyRecords(ontologyCompilation.ontology).sort((a, b) =>
354
+ a.id.localeCompare(b.id)
355
+ )) {
356
+ const id = ontologyGraphNodeId(ontologyId)
357
+ const parsedId = parseOntologyId(ontologyId)
358
+ pushUniqueNode(nodes, nodeIds, {
359
+ id,
360
+ kind: 'ontology',
361
+ label: ontologyLabel(ontologyId, record.label),
362
+ sourceId: ontologyId,
363
+ description: record.description,
364
+ ontologyKind: kind
365
+ })
366
+
367
+ const ownerSystemId = record.ownerSystemId ?? (parsedId.isGlobal ? undefined : parsedId.scope)
368
+ if (ownerSystemId !== undefined && validSystemRefs.has(ownerSystemId)) {
369
+ pushUniqueEdge(edges, edgeIds, {
370
+ id: edgeId('contains', systemNodeId(ownerSystemId), id, `ontology-${kind}`),
371
+ kind: 'contains',
372
+ sourceId: systemNodeId(ownerSystemId),
373
+ targetId: id
374
+ })
375
+ } else {
376
+ pushUniqueEdge(edges, edgeIds, {
377
+ id: edgeId('contains', organizationNode.id, id, `ontology-${kind}`),
378
+ kind: 'contains',
379
+ sourceId: organizationNode.id,
380
+ targetId: id
381
+ })
382
+ }
383
+
384
+ if (kind === 'link') {
385
+ const link = record as OntologyLinkType
386
+ pushUniqueEdge(edges, edgeIds, {
387
+ id: edgeId('links', ontologyGraphNodeId(link.from), ontologyGraphNodeId(link.to), ontologyId),
388
+ kind: 'links',
389
+ sourceId: ontologyGraphNodeId(link.from),
390
+ targetId: ontologyGraphNodeId(link.to),
391
+ label: link.label ?? parsedId.localId
392
+ })
393
+ }
394
+
395
+ if (kind === 'action') {
396
+ const action = record as OntologyActionType
397
+ for (const targetOntologyId of action.actsOn ?? []) {
398
+ pushUniqueEdge(edges, edgeIds, {
399
+ id: edgeId('affects', id, ontologyGraphNodeId(targetOntologyId), ontologyId),
400
+ kind: 'affects',
401
+ sourceId: id,
402
+ targetId: ontologyGraphNodeId(targetOntologyId)
403
+ })
404
+ }
405
+ }
406
+
407
+ if (kind === 'catalog') {
408
+ const catalog = record as OntologyCatalogType
409
+ if (catalog.appliesTo === undefined) continue
410
+ pushUniqueEdge(edges, edgeIds, {
411
+ id: edgeId('applies_to', id, ontologyGraphNodeId(catalog.appliesTo), ontologyId),
412
+ kind: 'applies_to',
413
+ sourceId: id,
414
+ targetId: ontologyGraphNodeId(catalog.appliesTo)
415
+ })
416
+ }
417
+
418
+ if (kind === 'group') {
419
+ const group = record as OntologyGroup
420
+ for (const memberOntologyId of group.members ?? []) {
421
+ pushUniqueEdge(edges, edgeIds, {
422
+ id: edgeId('contains', id, ontologyGraphNodeId(memberOntologyId), ontologyId),
423
+ kind: 'contains',
424
+ sourceId: id,
425
+ targetId: ontologyGraphNodeId(memberOntologyId)
426
+ })
427
+ }
428
+ }
429
+ }
430
430
 
431
431
  for (const role of Object.values(organizationModel.roles).sort((a, b) => a.id.localeCompare(b.id))) {
432
432
  const id = nodeId('role', role.id)
@@ -479,17 +479,17 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
479
479
  kind: 'contains',
480
480
  sourceId: organizationNode.id,
481
481
  targetId: id
482
- })
483
- for (const link of node.links) {
484
- const targetId =
485
- link.target.kind === 'system'
486
- ? systemNodeId(link.target.id)
487
- : link.target.kind === 'ontology'
488
- ? ontologyGraphNodeId(link.target.id)
489
- : link.nodeId
490
- pushUniqueEdge(edges, edgeIds, {
491
- id: edgeId('governs', id, targetId),
492
- kind: 'governs',
482
+ })
483
+ for (const link of node.links) {
484
+ const targetId =
485
+ link.target.kind === 'system'
486
+ ? systemNodeId(link.target.id)
487
+ : link.target.kind === 'ontology'
488
+ ? ontologyGraphNodeId(link.target.id)
489
+ : link.nodeId
490
+ pushUniqueEdge(edges, edgeIds, {
491
+ id: edgeId('governs', id, targetId),
492
+ kind: 'governs',
493
493
  sourceId: id,
494
494
  targetId
495
495
  })
@@ -596,9 +596,8 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
596
596
  if (entity.stateCatalogId !== undefined) {
597
597
  const stateEvents: EventDescriptor[] = []
598
598
 
599
- // Phase 4: model.statuses removed (D1). Compatibility status data can
600
- // still project from System.content; primary status-flow authoring now
601
- // belongs in System.ontology catalogTypes.
599
+ // Phase 4: model.statuses removed (D1). Status-flow authoring belongs
600
+ // in System.ontology catalogTypes.
602
601
 
603
602
  if (entity.stateCatalogId === 'crm.pipeline') {
604
603
  // Phase 4: model.sales removed (D8). Read pipelines via migration helper.
@@ -622,7 +621,7 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
622
621
 
623
622
  if (entity.stateCatalogId === 'lead-gen.company' || entity.stateCatalogId === 'lead-gen.contact') {
624
623
  const leadGenEntity = entity.stateCatalogId === 'lead-gen.company' ? 'company' : 'contact'
625
- for (const stage of Object.values(LEAD_GEN_STAGE_CATALOG).sort(
624
+ for (const stage of Object.values(getLeadGenStageCatalog(organizationModel)).sort(
626
625
  (a, b) => a.order - b.order || a.key.localeCompare(b.key)
627
626
  )) {
628
627
  if (stage.entity !== leadGenEntity && !(stage.additionalEntities ?? []).includes(leadGenEntity)) continue
@@ -673,22 +672,22 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
673
672
 
674
673
  // Skip the contains edge if the systemPath doesn't resolve to a real system.
675
674
  // In a fully-validated model this never fires; it guards partial-OM fixtures.
676
- if (validSystemPaths.has(resource.systemPath)) {
677
- pushUniqueEdge(edges, edgeIds, {
678
- id: edgeId('contains', systemNodeId(resource.systemPath), resourceNode.id, 'system-resource'),
679
- kind: 'contains',
680
- sourceId: systemNodeId(resource.systemPath),
681
- targetId: resourceNode.id
682
- })
683
- }
684
-
685
- pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'actions', resource.ontology?.actions)
686
- pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'reads', resource.ontology?.reads)
687
- pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'writes', resource.ontology?.writes)
688
- pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'uses_catalog', resource.ontology?.usesCatalogs)
689
- pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'emits', resource.ontology?.emits)
690
-
691
- if (resource.kind === 'workflow' || resource.kind === 'agent') {
675
+ if (validSystemPaths.has(resource.systemPath)) {
676
+ pushUniqueEdge(edges, edgeIds, {
677
+ id: edgeId('contains', systemNodeId(resource.systemPath), resourceNode.id, 'system-resource'),
678
+ kind: 'contains',
679
+ sourceId: systemNodeId(resource.systemPath),
680
+ targetId: resourceNode.id
681
+ })
682
+ }
683
+
684
+ pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'actions', resource.ontology?.actions)
685
+ pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'reads', resource.ontology?.reads)
686
+ pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'writes', resource.ontology?.writes)
687
+ pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'uses_catalog', resource.ontology?.usesCatalogs)
688
+ pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'emits', resource.ontology?.emits)
689
+
690
+ if (resource.kind === 'workflow' || resource.kind === 'agent') {
692
691
  for (const emission of resource.emits ?? []) {
693
692
  pushEventProjection(
694
693
  nodes,
@@ -731,9 +730,9 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
731
730
  }
732
731
  }
733
732
 
734
- for (const policy of Object.values(organizationModel.policies).sort(
735
- (a, b) => a.order - b.order || a.id.localeCompare(b.id)
736
- )) {
733
+ for (const policy of Object.values(organizationModel.policies).sort(
734
+ (a, b) => a.order - b.order || a.id.localeCompare(b.id)
735
+ )) {
737
736
  const id = nodeId('policy', policy.id)
738
737
  pushUniqueNode(nodes, nodeIds, {
739
738
  id,
@@ -820,26 +819,26 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
820
819
  targetId: nodeId('role', effect.roleId),
821
820
  label: effect.kind
822
821
  })
823
- }
824
- }
825
- }
826
-
827
- for (const [relationshipId, relationship] of Object.entries(organizationModel.topology.relationships).sort(([a], [b]) =>
828
- a.localeCompare(b)
829
- )) {
830
- const sourceId = ensureTopologyNode(relationship.from)
831
- const targetId = ensureTopologyNode(relationship.to)
832
-
833
- pushUniqueEdge(edges, edgeIds, {
834
- id: edgeId(relationship.kind, sourceId, targetId, `topology-${relationshipId}`),
835
- kind: relationship.kind,
836
- sourceId,
837
- targetId,
838
- relationshipType: relationship.kind
839
- })
840
- }
841
-
842
- for (const segment of Object.values(organizationModel.customers).sort(
822
+ }
823
+ }
824
+ }
825
+
826
+ for (const [relationshipId, relationship] of Object.entries(organizationModel.topology.relationships).sort(([a], [b]) =>
827
+ a.localeCompare(b)
828
+ )) {
829
+ const sourceId = ensureTopologyNode(relationship.from)
830
+ const targetId = ensureTopologyNode(relationship.to)
831
+
832
+ pushUniqueEdge(edges, edgeIds, {
833
+ id: edgeId(relationship.kind, sourceId, targetId, `topology-${relationshipId}`),
834
+ kind: relationship.kind,
835
+ sourceId,
836
+ targetId,
837
+ relationshipType: relationship.kind
838
+ })
839
+ }
840
+
841
+ for (const segment of Object.values(organizationModel.customers).sort(
843
842
  (a, b) => a.order - b.order || a.id.localeCompare(b.id)
844
843
  )) {
845
844
  const id = nodeId('customer-segment', segment.id)
@@ -961,78 +960,8 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
961
960
  targetId: id
962
961
  })
963
962
  }
964
-
965
- // ---------------------------------------------------------------------------
966
- // Compatibility bridge: content-node graph projection
967
- // ---------------------------------------------------------------------------
968
- // Keep bridge-era System.content addressable as read-only content-node:* graph
969
- // IDs for old UI and knowledge consumers. New semantic graph consumers should
970
- // prefer ontology-native graph nodes emitted from System.ontology.
971
- //
972
- // For every system in the model tree, emit a 'content-node' graph node for
973
- // each entry in system.content[*]. Edges:
974
- // - contains: system spine node → content-node
975
- // - contains: parent content-node → child content-node (via parentContentId)
976
- // - references: pipeline content-node → entity node (when data.entityId set)
977
- // ---------------------------------------------------------------------------
978
- for (const { path, system } of listAllSystems(organizationModel).sort((a, b) => a.path.localeCompare(b.path))) {
979
- const contentMap = system.content ?? {}
980
- const systemSpineId = nodeId('system', path)
981
-
982
- for (const [localId, contentNode] of Object.entries(contentMap).sort(([a], [b]) => a.localeCompare(b))) {
983
- const contentNodeGraphId = `content-node:${path}:${localId}`
984
-
985
- pushUniqueNode(nodes, nodeIds, {
986
- id: contentNodeGraphId,
987
- kind: 'content-node',
988
- label: contentNode.label,
989
- sourceId: `${path}:${localId}`,
990
- description: contentNode.description
991
- // Spread contentKind and contentType into attributes; the node schema
992
- // does not have custom attribute slots, so we encode them in the label
993
- // suffix for now. The actual kind/type are recoverable from sourceId +
994
- // the registry lookup by consumers.
995
- })
996
-
997
- // contains: system spine → content-node
998
- pushUniqueEdge(edges, edgeIds, {
999
- id: edgeId('contains', systemSpineId, contentNodeGraphId, 'system-content'),
1000
- kind: 'contains',
1001
- sourceId: systemSpineId,
1002
- targetId: contentNodeGraphId
1003
- })
1004
-
1005
- // contains: parent content-node → child content-node (parentContentId chain)
1006
- if (contentNode.parentContentId) {
1007
- const parentContentNodeGraphId = `content-node:${path}:${contentNode.parentContentId}`
1008
- pushUniqueEdge(edges, edgeIds, {
1009
- id: edgeId('contains', parentContentNodeGraphId, contentNodeGraphId, 'content-parent'),
1010
- kind: 'contains',
1011
- sourceId: parentContentNodeGraphId,
1012
- targetId: contentNodeGraphId
1013
- })
1014
- }
1015
-
1016
- // references: pipeline -> entity (data.entityId ref-field annotation)
1017
- // Bridge schema:pipeline payloadSchema carries `.meta({ ref: 'entity' })` on entityId.
1018
- // Emit a 'references' edge from the pipeline content-node to the entity node.
1019
- if (
1020
- contentNode.kind === 'schema' &&
1021
- contentNode.type === 'pipeline' &&
1022
- contentNode.data &&
1023
- typeof contentNode.data['entityId'] === 'string'
1024
- ) {
1025
- const targetEntityId = nodeId('entity', contentNode.data['entityId'])
1026
- pushUniqueEdge(edges, edgeIds, {
1027
- id: edgeId('references', contentNodeGraphId, targetEntityId, 'pipeline-entity'),
1028
- kind: 'references',
1029
- sourceId: contentNodeGraphId,
1030
- targetId: targetEntityId,
1031
- label: 'applies to entity'
1032
- })
1033
- }
1034
- }
1035
- }
963
+ // System.content bridge projection removed; ontology catalog nodes now carry
964
+ // operational catalog identity in the graph.
1036
965
 
1037
966
  // Phase 4: prospecting domain removed; read templates via migration helper.
1038
967
  // Steps are typed as BuildTemplate['steps'] which carries ProspectingBuildTemplateStepSchema fields.
@@ -18,9 +18,8 @@ export const OrganizationGraphNodeKindSchema = z.enum([
18
18
  'goal',
19
19
  'surface',
20
20
  'navigation-group',
21
- // Phase 3 preview Phase 4 populates via graph projection of system.content entries.
22
- 'ontology',
23
- 'content-node'
21
+ // Ontology records are projected from compiled System.ontology scopes.
22
+ 'ontology'
24
23
  ])
25
24
 
26
25
  export const OrganizationGraphEdgeKindSchema = z.enum([
@@ -19,9 +19,7 @@ export type OrganizationGraphNodeKind =
19
19
  | 'goal'
20
20
  | 'surface'
21
21
  | 'navigation-group'
22
- /** Phase 3 preview — Phase 4 populates this kind via graph projection of system.content entries. */
23
- | 'ontology'
24
- | 'content-node'
22
+ | 'ontology'
25
23
 
26
24
  export type OrganizationGraphEdgeKind =
27
25
  | 'contains'
@@ -32,15 +30,15 @@ export type OrganizationGraphEdgeKind =
32
30
  | 'links'
33
31
  | 'affects'
34
32
  | 'emits'
35
- | 'originates_from'
36
- | 'triggers'
37
- | 'approval'
38
- | 'applies_to'
39
- | 'effects'
40
- | 'actions'
41
- | 'reads'
42
- | 'writes'
43
- | 'uses_catalog'
33
+ | 'originates_from'
34
+ | 'triggers'
35
+ | 'approval'
36
+ | 'applies_to'
37
+ | 'effects'
38
+ | 'actions'
39
+ | 'reads'
40
+ | 'writes'
41
+ | 'uses_catalog'
44
42
  export type { Link } from './link'
45
43
 
46
44
  export interface OrganizationGraphNode {
@@ -50,8 +48,8 @@ export interface OrganizationGraphNode {
50
48
  sourceId?: string
51
49
  description?: string
52
50
  icon?: OrganizationModelIconToken
53
- enabled?: boolean
54
- ontologyKind?: string
51
+ enabled?: boolean
52
+ ontologyKind?: string
55
53
  resourceType?: 'workflow' | 'agent' | 'trigger' | 'integration' | 'external' | 'human_checkpoint' | 'script'
56
54
  }
57
55