@elevasis/core 0.24.1 → 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 (82) hide show
  1. package/dist/index.d.ts +239 -86
  2. package/dist/index.js +474 -1346
  3. package/dist/knowledge/index.d.ts +57 -39
  4. package/dist/knowledge/index.js +1 -1
  5. package/dist/organization-model/index.d.ts +239 -86
  6. package/dist/organization-model/index.js +474 -1346
  7. package/dist/test-utils/index.d.ts +24 -31
  8. package/dist/test-utils/index.js +76 -1238
  9. package/package.json +1 -1
  10. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +108 -96
  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/execution/engine/llm/adapters/__tests__/openrouter.integration.test.ts +10 -10
  18. package/src/knowledge/__tests__/queries.test.ts +960 -546
  19. package/src/knowledge/format.ts +322 -100
  20. package/src/knowledge/index.ts +18 -5
  21. package/src/knowledge/queries.ts +1004 -240
  22. package/src/organization-model/__tests__/content-kinds-registry.test.ts +35 -210
  23. package/src/organization-model/__tests__/defaults.test.ts +4 -4
  24. package/src/organization-model/__tests__/deprecate-helpers.test.ts +71 -0
  25. package/src/organization-model/__tests__/domains/actions.test.ts +12 -36
  26. package/src/organization-model/__tests__/domains/offerings.test.ts +13 -6
  27. package/src/organization-model/__tests__/domains/resources.test.ts +497 -350
  28. package/src/organization-model/__tests__/domains/systems.test.ts +6 -7
  29. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +68 -80
  30. package/src/organization-model/__tests__/foundation.test.ts +81 -14
  31. package/src/organization-model/__tests__/graph.test.ts +662 -694
  32. package/src/organization-model/__tests__/knowledge.test.ts +31 -17
  33. package/src/organization-model/__tests__/lookup-helpers.test.ts +128 -438
  34. package/src/organization-model/__tests__/migration-helpers.test.ts +362 -591
  35. package/src/organization-model/__tests__/prospecting-ssot.test.ts +68 -103
  36. package/src/organization-model/__tests__/published-zero-leak.test.ts +17 -0
  37. package/src/organization-model/__tests__/recursive-system-schema.test.ts +159 -532
  38. package/src/organization-model/__tests__/resolve.test.ts +88 -49
  39. package/src/organization-model/__tests__/scaffolders.test.ts +93 -0
  40. package/src/organization-model/__tests__/schema.test.ts +65 -56
  41. package/src/organization-model/catalogs/lead-gen.ts +0 -103
  42. package/src/organization-model/defaults.ts +17 -702
  43. package/src/organization-model/domains/actions.ts +116 -333
  44. package/src/organization-model/domains/knowledge.ts +15 -7
  45. package/src/organization-model/domains/projects.ts +4 -4
  46. package/src/organization-model/domains/prospecting.ts +405 -395
  47. package/src/organization-model/domains/resources.ts +206 -135
  48. package/src/organization-model/domains/sales.ts +5 -5
  49. package/src/organization-model/domains/systems.ts +8 -23
  50. package/src/organization-model/graph/build.ts +223 -294
  51. package/src/organization-model/graph/schema.ts +2 -3
  52. package/src/organization-model/graph/types.ts +12 -14
  53. package/src/organization-model/helpers.ts +120 -141
  54. package/src/organization-model/icons.ts +1 -0
  55. package/src/organization-model/index.ts +107 -126
  56. package/src/organization-model/migration-helpers.ts +211 -249
  57. package/src/organization-model/ontology.ts +0 -60
  58. package/src/organization-model/organization-graph.mdx +4 -5
  59. package/src/organization-model/organization-model.mdx +1 -1
  60. package/src/organization-model/published.ts +251 -228
  61. package/src/organization-model/resolve.ts +4 -5
  62. package/src/organization-model/scaffolders/helpers.ts +84 -0
  63. package/src/organization-model/scaffolders/index.ts +19 -0
  64. package/src/organization-model/scaffolders/scaffoldKnowledgeNode.ts +48 -0
  65. package/src/organization-model/scaffolders/scaffoldOntologyRecord.ts +38 -0
  66. package/src/organization-model/scaffolders/scaffoldResource.ts +59 -0
  67. package/src/organization-model/scaffolders/scaffoldSystem.ts +110 -0
  68. package/src/organization-model/scaffolders/types.ts +81 -0
  69. package/src/organization-model/schema.ts +610 -704
  70. package/src/organization-model/types.ts +167 -161
  71. package/src/platform/constants/versions.ts +1 -1
  72. package/src/platform/registry/__tests__/validation.test.ts +23 -0
  73. package/src/platform/registry/validation.ts +13 -2
  74. package/src/reference/_generated/contracts.md +108 -96
  75. package/src/reference/glossary.md +71 -69
  76. package/src/organization-model/content-kinds/config.ts +0 -36
  77. package/src/organization-model/content-kinds/index.ts +0 -78
  78. package/src/organization-model/content-kinds/pipeline.ts +0 -68
  79. package/src/organization-model/content-kinds/registry.ts +0 -44
  80. package/src/organization-model/content-kinds/status.ts +0 -71
  81. package/src/organization-model/content-kinds/template.ts +0 -83
  82. package/src/organization-model/content-kinds/types.ts +0 -117
@@ -1,252 +1,214 @@
1
- /**
2
- * Migration helpers — Phase 4 internals swap
3
- *
4
- * External signatures are UNCHANGED from the pre-Phase-4 era so callers remain
5
- * mechanical. Internally, compatibility reads for old compound-domain data
6
- * walk System.content via listAllSystems() and filter by (kind, type).
7
- *
8
- * Compatibility registry entries read:
9
- * 'schema:pipeline' — one pipeline per owning system
10
- * 'schema:stage' — stage within a pipeline; parentContentId = pipeline local id
11
- * 'schema:template' — prospecting build template
12
- * 'schema:template-step' — step within a template; parentContentId = template local id
13
- * 'schema:status-flow' — status set for a project/milestone/task scope
14
- * 'schema:status' — single status within a flow; parentContentId = status-flow local id
15
- */
16
-
1
+ /**
2
+ * Migration helpers
3
+ *
4
+ * External signatures are intentionally stable. Internally these helpers now
5
+ * read first-class ontology catalog records.
6
+ */
7
+
17
8
  import type { OrganizationModel } from './types'
18
9
  import type { z } from 'zod'
10
+ import type { LeadGenStageCatalogEntry } from './catalogs/lead-gen'
19
11
  import type { SalesPipelineSchema, SalesStageSchema } from './domains/sales'
20
- import type { ProspectingBuildTemplateSchema, ProspectingLifecycleStageSchema } from './domains/prospecting'
21
- import type { ProjectsDomainStateSchema } from './domains/projects'
22
- import { listAllSystems } from './helpers'
23
-
24
- // Compatibility boundary: these helpers intentionally keep old tenant
25
- // System.content schema nodes readable. New schema/catalog authoring belongs in
26
- // System.ontology, and new system-local settings belong in System.config.
27
-
28
- // ---------------------------------------------------------------------------
29
- // Locally-scoped inferred types — external signatures use these shapes.
30
- // ---------------------------------------------------------------------------
31
- type Pipeline = z.infer<typeof SalesPipelineSchema>
32
- type Stage = z.infer<typeof SalesStageSchema>
33
- type BuildTemplate = z.infer<typeof ProspectingBuildTemplateSchema>
34
- type ProspectingStage = z.infer<typeof ProspectingLifecycleStageSchema>
35
- type ProjectStatus = z.infer<typeof ProjectsDomainStateSchema>
36
-
37
- // ---------------------------------------------------------------------------
38
- // Sales — Pipelines + Stages
39
- // ---------------------------------------------------------------------------
40
-
41
- /**
42
- * Return all sales pipelines defined in the model, each tagged with the
43
- * system path of the owning system.
44
- *
45
- * Compatibility bridge: walks every system via listAllSystems(), finds content nodes where
46
- * (kind, type) === ('schema', 'pipeline'), and reconstructs the Pipeline shape
47
- * by pulling sibling stage content nodes (parentContentId === pipeline local id).
48
- */
49
- export function getAllPipelines(model: OrganizationModel): Array<{ systemPath: string; pipeline: Pipeline }> {
50
- const results: Array<{ systemPath: string; pipeline: Pipeline }> = []
51
-
52
- for (const { path: systemPath, system } of listAllSystems(model)) {
53
- const content = system.content ?? {}
54
-
55
- for (const [localId, node] of Object.entries(content)) {
56
- if (node.kind !== 'schema' || node.type !== 'pipeline') continue
57
-
58
- const data = (node.data ?? {}) as Record<string, unknown>
59
- const stages: Stage[] = Object.entries(content)
60
- .filter(([, s]) => s.kind === 'schema' && s.type === 'stage' && s.parentContentId === localId)
61
- .map(([stageLocalId, s]) => {
62
- const sd = (s.data ?? {}) as Record<string, unknown>
63
- return {
64
- id: stageLocalId,
65
- label: s.label ?? stageLocalId,
66
- order: typeof sd.order === 'number' ? sd.order : 0,
67
- semanticClass: (sd.semanticClass as Stage['semanticClass']) ?? 'open',
68
- surfaceIds: Array.isArray(sd.surfaceIds) ? (sd.surfaceIds as string[]) : [],
69
- resourceIds: Array.isArray(sd.resourceIds) ? (sd.resourceIds as string[]) : [],
70
- color: typeof sd.color === 'string' ? sd.color : undefined
71
- } satisfies Stage
72
- })
73
- .sort((a, b) => a.order - b.order)
74
-
75
- const pipeline: Pipeline = {
76
- id: localId,
77
- label: node.label ?? localId,
78
- entityId: typeof data.entityId === 'string' ? data.entityId : '',
79
- stages,
80
- ...(typeof node.description === 'string' ? { description: node.description } : {})
81
- }
82
-
83
- results.push({ systemPath, pipeline })
84
- }
85
- }
86
-
87
- return results
88
- }
89
-
90
- /**
91
- * Return the stages belonging to a given pipeline, identified by systemPath + pipelineLocalId.
92
- *
93
- * Compatibility bridge: resolves the system at systemPath, filters its content for stage nodes
94
- * where parentContentId === pipelineLocalId.
95
- *
96
- * @param model - The resolved organization model.
97
- * @param systemPath - Dot-separated path to the owning system (e.g. 'sales.crm').
98
- * @param pipelineLocalId - The local content id of the target pipeline node.
99
- */
100
- export function getStagesInPipeline(model: OrganizationModel, systemPath: string, pipelineLocalId: string): Stage[] {
101
- const allSystems = listAllSystems(model)
102
- const entry = allSystems.find((s) => s.path === systemPath)
103
- if (!entry) return []
104
-
105
- const content = entry.system.content ?? {}
106
- return Object.entries(content)
107
- .filter(([, node]) => node.kind === 'schema' && node.type === 'stage' && node.parentContentId === pipelineLocalId)
108
- .map(([stageLocalId, s]) => {
109
- const sd = (s.data ?? {}) as Record<string, unknown>
110
- return {
111
- id: stageLocalId,
112
- label: s.label ?? stageLocalId,
113
- order: typeof sd.order === 'number' ? sd.order : 0,
114
- semanticClass: (sd.semanticClass as Stage['semanticClass']) ?? 'open',
115
- surfaceIds: Array.isArray(sd.surfaceIds) ? (sd.surfaceIds as string[]) : [],
116
- resourceIds: Array.isArray(sd.resourceIds) ? (sd.resourceIds as string[]) : [],
117
- color: typeof sd.color === 'string' ? sd.color : undefined
118
- } satisfies Stage
119
- })
120
- .sort((a, b) => a.order - b.order)
121
- }
122
-
123
- // ---------------------------------------------------------------------------
124
- // Prospecting Build templates
125
- // ---------------------------------------------------------------------------
126
-
127
- /**
128
- * Return all prospecting build templates defined in the model.
129
- *
130
- * Compatibility bridge: walks every system, finds content nodes where
131
- * (kind, type) === ('schema', 'template'), reconstructs BuildTemplate shape
132
- * by pulling sibling template-step nodes (parentContentId === template local id).
133
- */
134
- export function getAllBuildTemplates(model: OrganizationModel): BuildTemplate[] {
135
- const results: BuildTemplate[] = []
136
-
137
- for (const { system } of listAllSystems(model)) {
138
- const content = system.content ?? {}
139
-
140
- for (const [localId, node] of Object.entries(content)) {
141
- if (node.kind !== 'schema' || node.type !== 'template') continue
142
- const nd = (node.data ?? {}) as Record<string, unknown>
143
-
144
- const steps = Object.entries(content)
145
- .filter(([, s]) => s.kind === 'schema' && s.type === 'template-step' && s.parentContentId === localId)
146
- .map(([stepLocalId, s]) => {
147
- const sd = (s.data ?? {}) as Record<string, unknown>
148
- return {
149
- id: stepLocalId,
150
- label: s.label ?? stepLocalId,
151
- ...(s.description ? { description: s.description } : {}),
152
- // Pass through all data fields — template-step payload is richer than Pipeline;
153
- // Bridge readers preserve the historical shape for round-trip fidelity.
154
- ...sd
155
- }
156
- })
157
-
158
- results.push({
159
- id: localId,
160
- label: node.label ?? localId,
161
- ...(node.description ? { description: node.description } : {}),
162
- ...(typeof nd.color === 'string' ? { color: nd.color } : {}),
163
- // BuildTemplate requires steps array cast via spread; Wave 4 adds type-safe assertions.
164
- steps: steps as BuildTemplate['steps']
165
- })
166
- }
167
- }
168
-
169
- return results
170
- }
171
-
172
- /**
173
- * Return the prospecting lifecycle stages for a given entity kind.
174
- *
175
- * Compatibility bridge: walks every system, finds content nodes where
176
- * (kind, type) === ('schema', 'stage') AND data.entityKind === kind.
177
- * Prospecting stages are distinguished from pipeline stages by the presence of
178
- * data.entityKind ('company' | 'contact') retained on bridge compatibility nodes.
179
- *
180
- * @param kind - 'company' or 'contact'.
181
- */
182
- export function getAllProspectingStages(model: OrganizationModel, kind: 'company' | 'contact'): ProspectingStage[] {
183
- const results: ProspectingStage[] = []
184
-
185
- for (const { system } of listAllSystems(model)) {
186
- const content = system.content ?? {}
187
-
188
- for (const [localId, node] of Object.entries(content)) {
189
- if (node.kind !== 'schema' || node.type !== 'stage') continue
190
- const sd = (node.data ?? {}) as Record<string, unknown>
191
- if (sd.entityKind !== kind) continue
192
-
193
- results.push({
194
- id: localId,
195
- label: node.label ?? localId,
196
- order: typeof sd.order === 'number' ? sd.order : 0,
197
- ...(typeof sd.color === 'string' ? { color: sd.color } : {}),
198
- ...(node.description ? { description: node.description } : {})
199
- } satisfies ProspectingStage)
200
- }
201
- }
202
-
203
- return results.sort((a, b) => a.order - b.order)
204
- }
205
-
206
- // ---------------------------------------------------------------------------
207
- // Projects — Statuses
208
- // ---------------------------------------------------------------------------
209
-
210
- /**
211
- * Return the project statuses for a given entity type.
212
- *
213
- * Compatibility bridge: walks every system, finds status-flow content nodes where
214
- * data.appliesTo === appliesTo, then collects their child status nodes
215
- * (parentContentId === status-flow local id), sorted by order.
216
- *
217
- * @param appliesTo - 'project', 'milestone', or 'task'.
218
- */
219
- export function getAllProjectStatuses(
220
- model: OrganizationModel,
221
- appliesTo: 'project' | 'milestone' | 'task'
222
- ): ProjectStatus[] {
223
- const results: ProjectStatus[] = []
224
-
225
- for (const { system } of listAllSystems(model)) {
226
- const content = system.content ?? {}
227
-
228
- // Find the status-flow node for this appliesTo scope.
229
- for (const [flowLocalId, flowNode] of Object.entries(content)) {
230
- if (flowNode.kind !== 'schema' || flowNode.type !== 'status-flow') continue
231
- const fd = (flowNode.data ?? {}) as Record<string, unknown>
232
- if (fd.appliesTo !== appliesTo) continue
233
-
234
- // Collect child status nodes.
235
- for (const [statusLocalId, statusNode] of Object.entries(content)) {
236
- if (statusNode.kind !== 'schema' || statusNode.type !== 'status') continue
237
- if (statusNode.parentContentId !== flowLocalId) continue
238
-
239
- const sd = (statusNode.data ?? {}) as Record<string, unknown>
240
- results.push({
241
- id: statusLocalId,
242
- label: statusNode.label ?? statusLocalId,
243
- order: typeof sd.order === 'number' ? sd.order : 0,
244
- ...(typeof sd.color === 'string' ? { color: sd.color } : {}),
245
- ...(statusNode.description ? { description: statusNode.description } : {})
246
- } satisfies ProjectStatus)
247
- }
248
- }
249
- }
250
-
251
- return results.sort((a, b) => a.order - b.order)
252
- }
12
+ import type { ProspectingBuildTemplateSchema, ProspectingLifecycleStageSchema } from './domains/prospecting'
13
+ import type { ProjectsDomainStateSchema } from './domains/projects'
14
+ import { compileOrganizationOntology, parseOntologyId, type OntologyCatalogType, type OntologyId } from './ontology'
15
+
16
+ type Pipeline = z.infer<typeof SalesPipelineSchema>
17
+ type Stage = z.infer<typeof SalesStageSchema>
18
+ type BuildTemplate = z.infer<typeof ProspectingBuildTemplateSchema>
19
+ type ProspectingStage = z.infer<typeof ProspectingLifecycleStageSchema>
20
+ type ProjectStatus = z.infer<typeof ProjectsDomainStateSchema>
21
+
22
+ type CatalogRecord = OntologyCatalogType & { origin?: { systemPath?: string } }
23
+ type CatalogEntry = Record<string, unknown>
24
+
25
+ function catalogRecords(model: OrganizationModel): CatalogRecord[] {
26
+ return Object.values(compileOrganizationOntology(model).ontology.catalogTypes) as CatalogRecord[]
27
+ }
28
+
29
+ function localId(id: string): string {
30
+ return parseOntologyId(id).localId
31
+ }
32
+
33
+ function systemScope(id: string, fallback?: string): string {
34
+ return parseOntologyId(id).scope || fallback || ''
35
+ }
36
+
37
+ function entriesOf(catalog: CatalogRecord): Array<[string, CatalogEntry]> {
38
+ return Object.entries(catalog.entries ?? {}).map(([id, value]) => [
39
+ id,
40
+ value && typeof value === 'object' && !Array.isArray(value) ? (value as CatalogEntry) : {}
41
+ ])
42
+ }
43
+
44
+ function stringValue(value: unknown): string | undefined {
45
+ return typeof value === 'string' ? value : undefined
46
+ }
47
+
48
+ function stringArray(value: unknown): string[] {
49
+ return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []
50
+ }
51
+
52
+ function numberValue(value: unknown, fallback = 0): number {
53
+ return typeof value === 'number' ? value : fallback
54
+ }
55
+
56
+ function stageFromEntry(entryId: string, entry: CatalogEntry): Stage {
57
+ return {
58
+ id: entryId,
59
+ label: stringValue(entry.label) ?? entryId,
60
+ order: numberValue(entry.order),
61
+ semanticClass: (stringValue(entry.semanticClass) as Stage['semanticClass']) ?? 'open',
62
+ surfaceIds: stringArray(entry.surfaceIds),
63
+ resourceIds: stringArray(entry.resourceIds),
64
+ color: stringValue(entry.color)
65
+ }
66
+ }
67
+
68
+ function appliesToLocalId(catalog: CatalogRecord): string | undefined {
69
+ const appliesTo = catalog.appliesTo
70
+ if (appliesTo === undefined) return undefined
71
+ return parseOntologyId(appliesTo).localId
72
+ }
73
+
74
+ function appliesToEntityKind(catalog: CatalogRecord): 'company' | 'contact' | 'project' | 'milestone' | 'task' | undefined {
75
+ const id = appliesToLocalId(catalog)
76
+ if (id === 'company' || id === 'contact' || id === 'project' || id === 'milestone' || id === 'task') return id
77
+ if (id === 'deal') return undefined
78
+ return undefined
79
+ }
80
+
81
+ export function getAllPipelines(model: OrganizationModel): Array<{ systemPath: string; pipeline: Pipeline }> {
82
+ return catalogRecords(model)
83
+ .filter((catalog) => catalog.kind === 'pipeline')
84
+ .map((catalog) => {
85
+ const pipeline: Pipeline = {
86
+ id: localId(catalog.id),
87
+ label: catalog.label ?? localId(catalog.id),
88
+ ...(catalog.description ? { description: catalog.description } : {}),
89
+ entityId: appliesToLocalId(catalog) ?? '',
90
+ stages: entriesOf(catalog)
91
+ .map(([entryId, entry]) => stageFromEntry(entryId, entry))
92
+ .sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))
93
+ }
94
+
95
+ return { systemPath: catalog.ownerSystemId ?? systemScope(catalog.id), pipeline }
96
+ })
97
+ .sort((a, b) => a.systemPath.localeCompare(b.systemPath) || a.pipeline.id.localeCompare(b.pipeline.id))
98
+ }
99
+
100
+ export function getStagesInPipeline(model: OrganizationModel, systemPath: string, pipelineLocalId: string): Stage[] {
101
+ const catalog = catalogRecords(model).find(
102
+ (record) =>
103
+ record.kind === 'pipeline' &&
104
+ (record.ownerSystemId ?? systemScope(record.id)) === systemPath &&
105
+ localId(record.id) === pipelineLocalId
106
+ )
107
+
108
+ if (catalog === undefined) return []
109
+
110
+ return entriesOf(catalog)
111
+ .map(([entryId, entry]) => stageFromEntry(entryId, entry))
112
+ .sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))
113
+ }
114
+
115
+ export function getAllBuildTemplates(model: OrganizationModel): BuildTemplate[] {
116
+ const catalogs = catalogRecords(model)
117
+ const stepCatalogs = new Map<OntologyId, CatalogRecord>(
118
+ catalogs.filter((catalog) => catalog.kind === 'template-step').map((catalog) => [catalog.id as OntologyId, catalog])
119
+ )
120
+
121
+ return catalogs
122
+ .filter((catalog) => catalog.kind === 'template')
123
+ .flatMap((catalog) =>
124
+ entriesOf(catalog).map(([templateId, templateEntry]) => {
125
+ const stepCatalogId = stringValue(templateEntry.stepCatalog) as OntologyId | undefined
126
+ const stepCatalog = stepCatalogId !== undefined ? stepCatalogs.get(stepCatalogId) : undefined
127
+ const steps = stepCatalog === undefined ? [] : entriesOf(stepCatalog)
128
+
129
+ return {
130
+ id: templateId,
131
+ label: stringValue(templateEntry.label) ?? templateId,
132
+ ...(stringValue(templateEntry.description) ? { description: stringValue(templateEntry.description) } : {}),
133
+ ...(stringValue(templateEntry.color) ? { color: stringValue(templateEntry.color) } : {}),
134
+ steps: steps.map(([stepId, step]) => ({
135
+ id: stepId,
136
+ label: stringValue(step.label) ?? stepId,
137
+ ...(stringValue(step.description) ? { description: stringValue(step.description) } : {}),
138
+ ...step
139
+ })) as BuildTemplate['steps']
140
+ } satisfies BuildTemplate
141
+ })
142
+ )
143
+ .sort((a, b) => a.id.localeCompare(b.id))
144
+ }
145
+
146
+ export function getAllProspectingStages(model: OrganizationModel, kind: 'company' | 'contact'): ProspectingStage[] {
147
+ return catalogRecords(model)
148
+ .filter((catalog) => catalog.kind === 'stage' && appliesToEntityKind(catalog) === kind)
149
+ .flatMap((catalog) =>
150
+ entriesOf(catalog).map(([entryId, entry]) => ({
151
+ id: entryId,
152
+ label: stringValue(entry.label) ?? entryId,
153
+ order: numberValue(entry.order),
154
+ ...(stringValue(entry.color) ? { color: stringValue(entry.color) } : {}),
155
+ ...(stringValue(entry.description) ? { description: stringValue(entry.description) } : {})
156
+ }))
157
+ )
158
+ .sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))
159
+ }
160
+
161
+ export function getLeadGenStageCatalog(model: OrganizationModel): Record<string, LeadGenStageCatalogEntry> {
162
+ const results: Record<string, LeadGenStageCatalogEntry> = {}
163
+
164
+ for (const catalog of catalogRecords(model).filter(
165
+ (record) => record.kind === 'stage' && (record.ownerSystemId ?? systemScope(record.id)) === 'sales.lead-gen'
166
+ )) {
167
+ const catalogEntity = appliesToEntityKind(catalog)
168
+ if (catalogEntity !== 'company' && catalogEntity !== 'contact') continue
169
+
170
+ for (const [entryId, entry] of entriesOf(catalog)) {
171
+ const entity =
172
+ entry.entity === 'contact' ? 'contact' : entry.entity === 'company' ? 'company' : catalogEntity
173
+ const additionalEntities = stringArray(entry.additionalEntities).filter(
174
+ (item): item is 'company' | 'contact' => item === 'company' || item === 'contact'
175
+ )
176
+ const recordEntity =
177
+ entry.recordEntity === 'company' || entry.recordEntity === 'contact' ? entry.recordEntity : undefined
178
+ const recordStageKey = stringValue(entry.recordStageKey)
179
+
180
+ results[entryId] = {
181
+ key: entryId,
182
+ label: stringValue(entry.label) ?? entryId,
183
+ description: stringValue(entry.description) ?? '',
184
+ order: numberValue(entry.order),
185
+ entity,
186
+ ...(additionalEntities.length > 0 ? { additionalEntities } : {}),
187
+ ...(recordEntity ? { recordEntity } : {}),
188
+ ...(recordStageKey ? { recordStageKey } : {})
189
+ }
190
+ }
191
+ }
192
+
193
+ return Object.fromEntries(
194
+ Object.entries(results).sort(([, a], [, b]) => a.order - b.order || a.key.localeCompare(b.key))
195
+ )
196
+ }
197
+
198
+ export function getAllProjectStatuses(
199
+ model: OrganizationModel,
200
+ appliesTo: 'project' | 'milestone' | 'task'
201
+ ): ProjectStatus[] {
202
+ return catalogRecords(model)
203
+ .filter((catalog) => catalog.kind === 'status-flow' && appliesToEntityKind(catalog) === appliesTo)
204
+ .flatMap((catalog) =>
205
+ entriesOf(catalog).map(([entryId, entry]) => ({
206
+ id: entryId,
207
+ label: stringValue(entry.label) ?? entryId,
208
+ order: numberValue(entry.order),
209
+ ...(stringValue(entry.color) ? { color: stringValue(entry.color) } : {}),
210
+ ...(stringValue(entry.description) ? { description: stringValue(entry.description) } : {})
211
+ }))
212
+ )
213
+ .sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))
214
+ }
@@ -292,7 +292,6 @@ type SystemLike = {
292
292
  title?: string
293
293
  description?: string
294
294
  ontology?: OntologyScope
295
- content?: Record<string, ContentLike>
296
295
  systems?: Record<string, SystemLike>
297
296
  subsystems?: Record<string, SystemLike>
298
297
  }
@@ -319,15 +318,6 @@ type ActionLike = {
319
318
  lifecycle?: string
320
319
  }
321
320
 
322
- type ContentLike = {
323
- kind: string
324
- type: string
325
- label?: string
326
- description?: string
327
- parentContentId?: string
328
- data?: Record<string, unknown>
329
- }
330
-
331
321
  type SourceContext = {
332
322
  source: string
333
323
  path: Array<string | number>
@@ -568,55 +558,6 @@ function addLegacyActionProjections(
568
558
  }
569
559
  }
570
560
 
571
- function addSystemContentProjections(
572
- index: ResolvedOntologyIndex,
573
- diagnostics: OntologyDiagnostic[],
574
- sourcesById: Map<string, SourceContext>,
575
- systemPath: string,
576
- system: SystemLike,
577
- schemaPath: Array<string | number>
578
- ): void {
579
- const content = system.content ?? {}
580
- for (const [localId, node] of Object.entries(content)) {
581
- if (node.kind !== 'schema') continue
582
-
583
- const entries = Object.fromEntries(
584
- Object.entries(content)
585
- .filter(([, candidate]) => candidate.parentContentId === localId)
586
- .map(([entryId, candidate]) => [
587
- entryId,
588
- {
589
- label: candidate.label ?? entryId,
590
- type: candidate.type,
591
- ...(candidate.description !== undefined ? { description: candidate.description } : {}),
592
- ...(candidate.data !== undefined ? candidate.data : {})
593
- }
594
- ])
595
- )
596
-
597
- const catalogType: OntologyCatalogType = {
598
- id: formatOntologyId({ scope: systemPath, kind: 'catalog', localId }),
599
- label: node.label ?? localId,
600
- description: node.description,
601
- ownerSystemId: systemPath,
602
- kind: node.type,
603
- ...(typeof node.data?.['entityId'] === 'string'
604
- ? { appliesTo: formatOntologyId({ scope: systemPath, kind: 'object', localId: node.data['entityId'] }) }
605
- : {}),
606
- ...(Object.keys(entries).length > 0 ? { entries } : {}),
607
- ...(node.data !== undefined ? { data: node.data } : {})
608
- }
609
-
610
- addRecord(index, diagnostics, sourcesById, 'catalogTypes', catalogType, {
611
- source: 'legacy.system.content',
612
- path: [...schemaPath, 'content', localId],
613
- kind: 'projected',
614
- systemPath,
615
- legacyId: `${systemPath}:${localId}`
616
- })
617
- }
618
- }
619
-
620
561
  function addSystemScopes(
621
562
  index: ResolvedOntologyIndex,
622
563
  diagnostics: OntologyDiagnostic[],
@@ -632,7 +573,6 @@ function addSystemScopes(
632
573
  ...currentPath,
633
574
  'ontology'
634
575
  ])
635
- addSystemContentProjections(index, diagnostics, sourcesById, systemPath, system, currentPath)
636
576
  addSystemScopes(index, diagnostics, sourcesById, childSystemsOf(system), systemPath, [
637
577
  ...currentPath,
638
578
  system.systems !== undefined ? 'systems' : 'subsystems'
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Organization Graph
3
- description: Organization OS graph layer documentation for the organization graph derived from Organization Model Systems, resources, actions, entities, content nodes, and typed links.
3
+ description: Organization OS graph layer documentation for the organization graph derived from Organization Model Systems, resources, actions, entities, ontology catalogs, and typed links.
4
4
  ---
5
5
 
6
6
  ## Overview
@@ -38,7 +38,6 @@ Node kinds:
38
38
  - `goal`
39
39
  - `surface`
40
40
  - `navigation-group`
41
- - `content-node`
42
41
 
43
42
  Edge kinds:
44
43
 
@@ -58,7 +57,7 @@ Edge kinds:
58
57
 
59
58
  System nodes come from the id-keyed `OrganizationModel.systems` map. Their graph IDs use `system:<id>`, such as `system:sales.crm`.
60
59
 
61
- Resource, action, entity, policy, navigation, and content edges are derived from canonical OM maps:
60
+ Resource, action, entity, policy, navigation, and ontology catalog edges are derived from canonical OM maps:
62
61
 
63
62
  ```ts
64
63
  // ResourceEntry.systemPath => system -> resource contains
@@ -67,7 +66,7 @@ Resource, action, entity, policy, navigation, and content edges are derived from
67
66
  // Action.affects[] => action -> entity affects
68
67
  // Entity.links[] => entity -> entity links
69
68
  // ResourceEntry.emits[] => resource -> event emits
70
- // System.content => system -> content-node contains
69
+ // System.ontology.catalogTypes => ontology catalog nodes and relationships
71
70
  // AgentResource.invocations[] => agent resource -> invocation target uses/references
72
71
  ```
73
72
 
@@ -84,7 +83,7 @@ interface BuildOrganizationGraphInput {
84
83
 
85
84
  1. Reads Organization Model Systems and derives `system:*` nodes.
86
85
  2. Reads OM resources, including workflow, agent, integration, and script resources, as `resource` nodes.
87
- 3. Emits derived graph links from System, Resource, Action, Entity, Policy, Agent invocation, Knowledge, navigation, and System content contracts.
86
+ 3. Emits derived graph links from System, Resource, Action, Entity, Policy, Agent invocation, Knowledge, navigation, and ontology catalog contracts.
88
87
  4. Bridges Command View runtime topology into resource nodes and relationship edges.
89
88
  5. Returns a renderer-agnostic DTO.
90
89
 
@@ -179,7 +179,7 @@ The model keeps business semantics in named domains:
179
179
  - `policies` -- operational governance rules over systems, actions, resources, and roles
180
180
  - `knowledge` -- playbooks, strategies, references, and graph-governing links
181
181
 
182
- System-local operational catalogs now live under `System.content`, including pipeline, stage, template, template-step, status-flow, status, and config nodes. The legacy compound and status domains should not be used for new authoring.
182
+ System-local operational catalogs live under `System.ontology.catalogTypes`, including pipeline, stage, template, template-step, status-flow, and status records. System-local settings live in `System.config`. The legacy compound and status domains should not be used for new authoring.
183
183
 
184
184
  ## Authoring and Resolution
185
185