@elevasis/core 0.42.1 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/index.d.ts +8 -3
- package/dist/auth/index.js +6 -0
- package/dist/business/entities-published.d.ts +1 -1
- package/dist/index.d.ts +12 -13
- package/dist/index.js +48 -29
- package/dist/knowledge/index.d.ts +94 -6
- package/dist/knowledge/index.js +172 -8
- package/dist/organization-model/index.d.ts +12 -13
- package/dist/organization-model/index.js +48 -29
- package/dist/test-utils/index.d.ts +5 -6
- package/dist/test-utils/index.js +21 -18
- package/package.json +3 -3
- package/src/auth/access-keys.ts +6 -0
- package/src/business/acquisition/api-schemas.ts +1 -1
- package/src/business/base-entities.ts +1 -1
- package/src/knowledge/cli-helpers.ts +211 -0
- package/src/knowledge/index.ts +13 -0
- package/src/knowledge/published.ts +18 -5
- package/src/knowledge/queries.ts +5 -5
- package/src/organization-model/__tests__/cross-ref.test.ts +11 -1
- package/src/organization-model/__tests__/domains/systems.test.ts +34 -8
- package/src/organization-model/__tests__/scaffolders.test.ts +30 -1
- package/src/organization-model/__tests__/schema-refinements.test.ts +178 -0
- package/src/organization-model/cross-ref.ts +43 -7
- package/src/organization-model/defaults.ts +2 -2
- package/src/organization-model/domains/actions.ts +1 -1
- package/src/organization-model/domains/resources.ts +1 -1
- package/src/organization-model/domains/systems.ts +0 -4
- package/src/organization-model/ontology.ts +13 -18
- package/src/organization-model/organization-graph.mdx +9 -8
- package/src/organization-model/published.ts +9 -3
- package/src/organization-model/resolve.ts +9 -7
- package/src/organization-model/scaffolders/helpers.ts +1 -1
- package/src/organization-model/scaffolders/scaffoldKnowledgeNode.ts +1 -0
- package/src/organization-model/scaffolders/scaffoldOntologyRecord.ts +28 -6
- package/src/organization-model/scaffolders/scaffoldResource.ts +1 -0
- package/src/organization-model/scaffolders/scaffoldSystem.ts +2 -1
- package/src/organization-model/schema-refinements.ts +3 -5
- package/src/platform/registry/__tests__/validation.test.ts +28 -0
- package/src/platform/registry/validation.ts +20 -2
- package/src/scaffold-registry/__tests__/index.test.ts +380 -206
- package/src/scaffold-registry/index.ts +392 -381
- package/src/test-utils/mocks/supabase.ts +1 -1
- package/src/test-utils/mocks/workos.ts +2 -2
|
@@ -4,6 +4,15 @@ import { DEFAULT_ORGANIZATION_MODEL } from '../defaults'
|
|
|
4
4
|
import { refineOrganizationModel } from '../schema-refinements'
|
|
5
5
|
import type { OrganizationModel } from '../types'
|
|
6
6
|
|
|
7
|
+
type RefinementCase = {
|
|
8
|
+
name: string
|
|
9
|
+
model: OrganizationModel
|
|
10
|
+
expected: {
|
|
11
|
+
message: string
|
|
12
|
+
path: Array<string | number>
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
function collectRefinementIssues(model: OrganizationModel): z.ZodIssue[] {
|
|
8
17
|
const issues: z.ZodIssue[] = []
|
|
9
18
|
const ctx = {
|
|
@@ -16,6 +25,21 @@ function collectRefinementIssues(model: OrganizationModel): z.ZodIssue[] {
|
|
|
16
25
|
return issues
|
|
17
26
|
}
|
|
18
27
|
|
|
28
|
+
function makeModel(overrides: Partial<OrganizationModel>): OrganizationModel {
|
|
29
|
+
return {
|
|
30
|
+
...DEFAULT_ORGANIZATION_MODEL,
|
|
31
|
+
systems: {
|
|
32
|
+
test: {
|
|
33
|
+
id: 'test',
|
|
34
|
+
order: 10,
|
|
35
|
+
label: 'Test',
|
|
36
|
+
lifecycle: 'active'
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
...overrides
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
19
43
|
describe('refineOrganizationModel', () => {
|
|
20
44
|
it('emits resource systemPath issues through the extracted refinement boundary', () => {
|
|
21
45
|
const model: OrganizationModel = {
|
|
@@ -69,4 +93,158 @@ describe('refineOrganizationModel', () => {
|
|
|
69
93
|
true
|
|
70
94
|
)
|
|
71
95
|
})
|
|
96
|
+
|
|
97
|
+
it.each<RefinementCase>([
|
|
98
|
+
{
|
|
99
|
+
name: 'system parent cycle',
|
|
100
|
+
model: makeModel({
|
|
101
|
+
systems: {
|
|
102
|
+
a: { id: 'a', order: 10, label: 'A', parentSystemId: 'b' },
|
|
103
|
+
b: { id: 'b', order: 20, label: 'B', parentSystemId: 'a' }
|
|
104
|
+
}
|
|
105
|
+
}),
|
|
106
|
+
expected: {
|
|
107
|
+
path: ['systems', 'a', 'parentSystemId'],
|
|
108
|
+
message: 'System "a" has a parent cycle'
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'role reportsToId cycle',
|
|
113
|
+
model: makeModel({
|
|
114
|
+
roles: {
|
|
115
|
+
'role.a': { id: 'role.a', order: 10, title: 'Role A', reportsToId: 'role.b' },
|
|
116
|
+
'role.b': { id: 'role.b', order: 20, title: 'Role B', reportsToId: 'role.a' }
|
|
117
|
+
}
|
|
118
|
+
}),
|
|
119
|
+
expected: {
|
|
120
|
+
path: ['roles', 'role.a', 'reportsToId'],
|
|
121
|
+
message: 'Role "role.a" has a reportsToId cycle'
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'duplicate sidebar paths',
|
|
126
|
+
model: makeModel({
|
|
127
|
+
navigation: {
|
|
128
|
+
...DEFAULT_ORGANIZATION_MODEL.navigation,
|
|
129
|
+
sidebar: {
|
|
130
|
+
primary: {
|
|
131
|
+
first: { type: 'surface', label: 'First', path: '/shared', surfaceType: 'page', order: 10 },
|
|
132
|
+
second: { type: 'surface', label: 'Second', path: '/shared/', surfaceType: 'page', order: 20 }
|
|
133
|
+
},
|
|
134
|
+
bottom: {}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}),
|
|
138
|
+
expected: {
|
|
139
|
+
path: ['navigation', 'sidebar', 'primary', 'second', 'path'],
|
|
140
|
+
message: 'Sidebar surface path "/shared/" duplicates surface "first"'
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'topology invalid refs',
|
|
145
|
+
model: makeModel({
|
|
146
|
+
topology: {
|
|
147
|
+
version: 1,
|
|
148
|
+
relationships: {
|
|
149
|
+
missing: {
|
|
150
|
+
from: { kind: 'system', id: 'missing' },
|
|
151
|
+
kind: 'uses',
|
|
152
|
+
to: { kind: 'system', id: 'test' }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}),
|
|
157
|
+
expected: {
|
|
158
|
+
path: ['topology', 'relationships', 'missing', 'from'],
|
|
159
|
+
message: 'Topology relationship "missing" from references unknown system "missing"'
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'knowledge kind target mismatch',
|
|
164
|
+
model: makeModel({
|
|
165
|
+
roles: {
|
|
166
|
+
'role.ops': { id: 'role.ops', order: 10, title: 'Ops' }
|
|
167
|
+
},
|
|
168
|
+
knowledge: {
|
|
169
|
+
'knowledge.strategy': {
|
|
170
|
+
id: 'knowledge.strategy',
|
|
171
|
+
kind: 'strategy',
|
|
172
|
+
title: 'Strategy',
|
|
173
|
+
summary: 'Strategy summary',
|
|
174
|
+
body: 'Body',
|
|
175
|
+
updatedAt: '2026-06-04',
|
|
176
|
+
ownerIds: [],
|
|
177
|
+
links: [{ target: { kind: 'role', id: 'role.ops' }, nodeId: 'role:role.ops' }]
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}),
|
|
181
|
+
expected: {
|
|
182
|
+
path: ['knowledge', 'knowledge.strategy', 'links', 0, 'target', 'kind'],
|
|
183
|
+
message: 'Knowledge node "knowledge.strategy" kind "strategy" cannot govern role targets'
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'entity unknown system',
|
|
188
|
+
model: makeModel({
|
|
189
|
+
entities: {
|
|
190
|
+
lead: { id: 'lead', order: 10, label: 'Lead', ownedBySystemId: 'missing' }
|
|
191
|
+
}
|
|
192
|
+
}),
|
|
193
|
+
expected: {
|
|
194
|
+
path: ['entities', 'lead', 'ownedBySystemId'],
|
|
195
|
+
message: 'Entity "lead" references unknown ownedBySystemId "missing"'
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: 'policy unknown action',
|
|
200
|
+
model: makeModel({
|
|
201
|
+
policies: {
|
|
202
|
+
'policy.test': {
|
|
203
|
+
id: 'policy.test',
|
|
204
|
+
order: 10,
|
|
205
|
+
label: 'Test Policy',
|
|
206
|
+
trigger: { kind: 'action-invocation', actionId: 'missing.action' },
|
|
207
|
+
actions: [{ kind: 'invoke-action', actionId: 'missing.action' }],
|
|
208
|
+
appliesTo: { systemIds: [], actionIds: ['missing.action'], resourceIds: [], roleIds: [] }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}),
|
|
212
|
+
expected: {
|
|
213
|
+
path: ['policies', 'policy.test', 'appliesTo', 'actionIds', 0],
|
|
214
|
+
message: 'Policy "policy.test" applies to unknown action "missing.action"'
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'active system with no enabled descendants',
|
|
219
|
+
model: makeModel({
|
|
220
|
+
systems: {
|
|
221
|
+
parent: {
|
|
222
|
+
id: 'parent',
|
|
223
|
+
order: 10,
|
|
224
|
+
label: 'Parent',
|
|
225
|
+
lifecycle: 'active',
|
|
226
|
+
systems: {
|
|
227
|
+
child: { id: 'child', order: 10, label: 'Child', lifecycle: 'archived' }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}),
|
|
232
|
+
expected: {
|
|
233
|
+
path: ['systems', 'parent', 'lifecycle'],
|
|
234
|
+
message: 'System "parent" is active but has no active descendants'
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
])('emits focused refinement issues for $name', ({ model, expected }) => {
|
|
238
|
+
const issues = collectRefinementIssues(model)
|
|
239
|
+
|
|
240
|
+
expect(issues).toEqual(
|
|
241
|
+
expect.arrayContaining([
|
|
242
|
+
expect.objectContaining({
|
|
243
|
+
code: z.ZodIssueCode.custom,
|
|
244
|
+
path: expected.path,
|
|
245
|
+
message: expected.message
|
|
246
|
+
})
|
|
247
|
+
])
|
|
248
|
+
)
|
|
249
|
+
})
|
|
72
250
|
})
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { compileOrganizationOntology } from './ontology'
|
|
13
13
|
import { listAllSystems } from './helpers'
|
|
14
|
-
import type { OntologyKind } from './ontology'
|
|
14
|
+
import type { OntologyCompilation, OntologyKind } from './ontology'
|
|
15
15
|
import type { OrganizationModel } from './types'
|
|
16
16
|
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
@@ -36,7 +36,7 @@ export const ONTOLOGY_REFERENCE_KEY_KINDS: Record<string, OntologyKind> = {
|
|
|
36
36
|
interfaceType: 'interface',
|
|
37
37
|
propertyType: 'property',
|
|
38
38
|
groupType: 'group',
|
|
39
|
-
|
|
39
|
+
endpointType: 'endpoint',
|
|
40
40
|
stepCatalog: 'catalog'
|
|
41
41
|
} satisfies Record<string, OntologyKind>
|
|
42
42
|
|
|
@@ -69,12 +69,23 @@ export interface OmCrossRefIndex {
|
|
|
69
69
|
stageIds: Set<string>
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
export interface OmCompilationContext {
|
|
73
|
+
crossRefIndex: OmCrossRefIndex
|
|
74
|
+
ontologyCompilation: OntologyCompilation
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const omCompilationContextCache = new WeakMap<OrganizationModel, OmCompilationContext>()
|
|
78
|
+
|
|
72
79
|
/**
|
|
73
|
-
* Build the OmCrossRefIndex from a resolved OrganizationModel
|
|
80
|
+
* Build the OmCrossRefIndex from a resolved OrganizationModel and a compiled
|
|
81
|
+
* ontology result.
|
|
74
82
|
*
|
|
75
83
|
* Call once per validation pass and share the result across all checks.
|
|
76
84
|
*/
|
|
77
|
-
|
|
85
|
+
function buildOmCrossRefIndexFromOntology(
|
|
86
|
+
model: OrganizationModel,
|
|
87
|
+
ontologyCompilation: OntologyCompilation
|
|
88
|
+
): OmCrossRefIndex {
|
|
78
89
|
// Systems: keyed by path AND by system.id (path-OR-id resolution)
|
|
79
90
|
const systemsById = new Map<string, unknown>()
|
|
80
91
|
for (const { path, system } of listAllSystems(model)) {
|
|
@@ -91,8 +102,6 @@ export function buildOmCrossRefIndex(model: OrganizationModel): OmCrossRefIndex
|
|
|
91
102
|
const customerSegmentIds = new Set(Object.keys(model.customers ?? {}))
|
|
92
103
|
const offeringIds = new Set(Object.keys(model.offerings ?? {}))
|
|
93
104
|
|
|
94
|
-
const ontologyCompilation = compileOrganizationOntology(model)
|
|
95
|
-
|
|
96
105
|
const ontologyIndexByKind: Record<OntologyKind, Record<string, unknown>> = {
|
|
97
106
|
object: ontologyCompilation.ontology.objectTypes,
|
|
98
107
|
link: ontologyCompilation.ontology.linkTypes,
|
|
@@ -103,7 +112,7 @@ export function buildOmCrossRefIndex(model: OrganizationModel): OmCrossRefIndex
|
|
|
103
112
|
'value-type': ontologyCompilation.ontology.valueTypes,
|
|
104
113
|
property: ontologyCompilation.ontology.sharedProperties,
|
|
105
114
|
group: ontologyCompilation.ontology.groups,
|
|
106
|
-
|
|
115
|
+
endpoint: ontologyCompilation.ontology.endpoints
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
const ontologyIds = new Set(Object.values(ontologyIndexByKind).flatMap((index) => Object.keys(index)))
|
|
@@ -136,6 +145,33 @@ export function buildOmCrossRefIndex(model: OrganizationModel): OmCrossRefIndex
|
|
|
136
145
|
}
|
|
137
146
|
}
|
|
138
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Build and memoize the shared OM compilation context for a resolved model.
|
|
150
|
+
*
|
|
151
|
+
* This combines ontology compilation and cross-reference indexing so validation
|
|
152
|
+
* gates can reuse one ontology pass while preserving browser-safe imports.
|
|
153
|
+
*/
|
|
154
|
+
export function buildOmCompilationContext(model: OrganizationModel): OmCompilationContext {
|
|
155
|
+
const cached = omCompilationContextCache.get(model)
|
|
156
|
+
if (cached !== undefined) return cached
|
|
157
|
+
|
|
158
|
+
const ontologyCompilation = compileOrganizationOntology(model)
|
|
159
|
+
const crossRefIndex = buildOmCrossRefIndexFromOntology(model, ontologyCompilation)
|
|
160
|
+
const context = { crossRefIndex, ontologyCompilation }
|
|
161
|
+
omCompilationContextCache.set(model, context)
|
|
162
|
+
return context
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Build the OmCrossRefIndex from a resolved OrganizationModel.
|
|
167
|
+
*
|
|
168
|
+
* Back-compat helper retained for existing consumers. New validation paths that
|
|
169
|
+
* also need ontology diagnostics should prefer buildOmCompilationContext().
|
|
170
|
+
*/
|
|
171
|
+
export function buildOmCrossRefIndex(model: OrganizationModel): OmCrossRefIndex {
|
|
172
|
+
return buildOmCompilationContext(model).crossRefIndex
|
|
173
|
+
}
|
|
174
|
+
|
|
139
175
|
// ---------------------------------------------------------------------------
|
|
140
176
|
// Canonical resolution functions
|
|
141
177
|
// ---------------------------------------------------------------------------
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
*
|
|
8
8
|
* It does NOT contain Elevasis-specific identity, systems, or action entries.
|
|
9
9
|
* Runtime consumers that need the full Elevasis canonical model should import
|
|
10
|
-
* `canonicalOrganizationModel` from
|
|
10
|
+
* `canonicalOrganizationModel` from the tenant-owned organization model package instead.
|
|
11
11
|
*
|
|
12
12
|
* Elevasis-specific systems, actions (LEAD_GEN_ACTION_ENTRIES, CRM_ACTION_ENTRIES,
|
|
13
13
|
* DEFAULT_ORGANIZATION_MODEL_ACTIONS), and the platform system description have been
|
|
14
|
-
* relocated to
|
|
14
|
+
* relocated to the tenant-owned organization model package.
|
|
15
15
|
*/
|
|
16
16
|
import type { OrganizationModel } from './types'
|
|
17
17
|
import { DEFAULT_ORGANIZATION_MODEL_BRANDING } from './domains/branding'
|
|
@@ -96,7 +96,7 @@ export const ActionsDomainSchema = z
|
|
|
96
96
|
* Generic empty default for the actions domain.
|
|
97
97
|
* Elevasis-specific action entries (LEAD_GEN_ACTION_ENTRIES, CRM_ACTION_ENTRIES,
|
|
98
98
|
* DEFAULT_ORGANIZATION_MODEL_ACTIONS) have been relocated to
|
|
99
|
-
*
|
|
99
|
+
* the tenant-owned organization model package.
|
|
100
100
|
* Tenant OM configs supply their own action entries via `resolveOrganizationModel`.
|
|
101
101
|
*/
|
|
102
102
|
export const DEFAULT_ORGANIZATION_MODEL_ACTIONS: z.infer<typeof ActionsDomainSchema> = {}
|
|
@@ -123,7 +123,7 @@ export const ResourceOntologyBindingSchema = z
|
|
|
123
123
|
/**
|
|
124
124
|
* Optional typed contract binding for this resource's workflow I/O.
|
|
125
125
|
* Each ref is a `package/subpath#ExportName` string that resolves to a
|
|
126
|
-
* Zod schema in
|
|
126
|
+
* Zod schema in the tenant-owned organization model package.
|
|
127
127
|
*
|
|
128
128
|
* Absence of this field preserves all existing behavior — it is additive + optional.
|
|
129
129
|
* Tier-1 validation (schema.ts): ref-string shape only (browser-safe, no imports).
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { z, type ZodType } from 'zod'
|
|
2
2
|
import { ActionRefSchema } from './actions'
|
|
3
3
|
import {
|
|
4
|
-
ColorTokenSchema,
|
|
5
4
|
DescriptionSchema,
|
|
6
5
|
IconNameSchema,
|
|
7
6
|
LabelSchema,
|
|
@@ -156,7 +155,6 @@ export interface SystemEntry {
|
|
|
156
155
|
status?: 'active' | 'deprecated' | 'archived'
|
|
157
156
|
path?: string
|
|
158
157
|
icon?: string
|
|
159
|
-
color?: string
|
|
160
158
|
uiPosition?: 'sidebar-primary' | 'sidebar-bottom'
|
|
161
159
|
enabled?: boolean
|
|
162
160
|
devOnly?: boolean
|
|
@@ -213,8 +211,6 @@ export const SystemEntrySchema: ZodType<SystemEntry> = z
|
|
|
213
211
|
path: PathSchema.optional(),
|
|
214
212
|
/** @deprecated Use ui.icon. Kept for one-cycle Feature compatibility. */
|
|
215
213
|
icon: IconNameSchema.optional(),
|
|
216
|
-
/** @deprecated Feature color token, retained for one-cycle compatibility. */
|
|
217
|
-
color: ColorTokenSchema.optional(),
|
|
218
214
|
/** @deprecated UI placement hint, retained for one-cycle compatibility. */
|
|
219
215
|
uiPosition: UiPositionSchema.optional(),
|
|
220
216
|
/** @deprecated Use lifecycle. */
|
|
@@ -10,7 +10,7 @@ export const OntologyKindSchema = z.enum([
|
|
|
10
10
|
'value-type',
|
|
11
11
|
'property',
|
|
12
12
|
'group',
|
|
13
|
-
'
|
|
13
|
+
'endpoint'
|
|
14
14
|
])
|
|
15
15
|
|
|
16
16
|
export type OntologyKind = z.infer<typeof OntologyKindSchema>
|
|
@@ -25,10 +25,7 @@ export const OntologyIdSchema = z
|
|
|
25
25
|
.trim()
|
|
26
26
|
.min(1)
|
|
27
27
|
.max(300)
|
|
28
|
-
.regex(
|
|
29
|
-
ONTOLOGY_ID_REGEX,
|
|
30
|
-
'Ontology IDs must use <system-path>:<kind>/<local-id> or global:<kind>/<local-id>'
|
|
31
|
-
)
|
|
28
|
+
.regex(ONTOLOGY_ID_REGEX, 'Ontology IDs must use <system-path>:<kind>/<local-id> or global:<kind>/<local-id>')
|
|
32
29
|
|
|
33
30
|
export type OntologyId = z.infer<typeof OntologyIdSchema>
|
|
34
31
|
|
|
@@ -56,11 +53,7 @@ export function parseOntologyId(id: string): ParsedOntologyId {
|
|
|
56
53
|
}
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
export function formatOntologyId(input: {
|
|
60
|
-
scope: string
|
|
61
|
-
kind: OntologyKind
|
|
62
|
-
localId: string
|
|
63
|
-
}): OntologyId {
|
|
56
|
+
export function formatOntologyId(input: { scope: string; kind: OntologyKind; localId: string }): OntologyId {
|
|
64
57
|
return OntologyIdSchema.parse(`${input.scope}:${input.kind}/${input.localId}`)
|
|
65
58
|
}
|
|
66
59
|
|
|
@@ -122,7 +115,7 @@ export const OntologyGroupSchema = OntologyRecordBaseSchema.extend({
|
|
|
122
115
|
members: OntologyReferenceListSchema
|
|
123
116
|
})
|
|
124
117
|
|
|
125
|
-
export const
|
|
118
|
+
export const OntologyEndpointTypeSchema = OntologyRecordBaseSchema.extend({
|
|
126
119
|
route: z.string().trim().min(1).max(500).optional()
|
|
127
120
|
})
|
|
128
121
|
|
|
@@ -137,7 +130,7 @@ export const OntologyScopeSchema = z
|
|
|
137
130
|
valueTypes: z.record(OntologyIdSchema, OntologyValueTypeSchema).default({}).optional(),
|
|
138
131
|
sharedProperties: z.record(OntologyIdSchema, OntologySharedPropertySchema).default({}).optional(),
|
|
139
132
|
groups: z.record(OntologyIdSchema, OntologyGroupSchema).default({}).optional(),
|
|
140
|
-
|
|
133
|
+
endpoints: z.record(OntologyIdSchema, OntologyEndpointTypeSchema).default({}).optional()
|
|
141
134
|
})
|
|
142
135
|
.default({})
|
|
143
136
|
|
|
@@ -175,7 +168,7 @@ export type OntologyInterfaceType = z.infer<typeof OntologyInterfaceTypeSchema>
|
|
|
175
168
|
export type OntologyValueType = z.infer<typeof OntologyValueTypeSchema>
|
|
176
169
|
export type OntologySharedProperty = z.infer<typeof OntologySharedPropertySchema>
|
|
177
170
|
export type OntologyGroup = z.infer<typeof OntologyGroupSchema>
|
|
178
|
-
export type
|
|
171
|
+
export type OntologyEndpointType = z.infer<typeof OntologyEndpointTypeSchema>
|
|
179
172
|
export type OntologyScope = z.infer<typeof OntologyScopeSchema>
|
|
180
173
|
|
|
181
174
|
export type ResolvedOntologyIndex = {
|
|
@@ -188,7 +181,7 @@ export type ResolvedOntologyIndex = {
|
|
|
188
181
|
valueTypes: Record<OntologyId, ResolvedOntologyRecord<OntologyValueType>>
|
|
189
182
|
sharedProperties: Record<OntologyId, ResolvedOntologyRecord<OntologySharedProperty>>
|
|
190
183
|
groups: Record<OntologyId, ResolvedOntologyRecord<OntologyGroup>>
|
|
191
|
-
|
|
184
|
+
endpoints: Record<OntologyId, ResolvedOntologyRecord<OntologyEndpointType>>
|
|
192
185
|
}
|
|
193
186
|
|
|
194
187
|
export type OntologyRecordOrigin = {
|
|
@@ -230,7 +223,7 @@ export type ResolvedOntologyRecordEntry = {
|
|
|
230
223
|
| OntologyValueType
|
|
231
224
|
| OntologySharedProperty
|
|
232
225
|
| OntologyGroup
|
|
233
|
-
|
|
|
226
|
+
| OntologyEndpointType
|
|
234
227
|
>
|
|
235
228
|
}
|
|
236
229
|
|
|
@@ -246,7 +239,7 @@ const SCOPE_KIND: Record<ScopeKey, OntologyKind> = {
|
|
|
246
239
|
valueTypes: 'value-type',
|
|
247
240
|
sharedProperties: 'property',
|
|
248
241
|
groups: 'group',
|
|
249
|
-
|
|
242
|
+
endpoints: 'endpoint'
|
|
250
243
|
}
|
|
251
244
|
|
|
252
245
|
const SCOPE_KEYS = Object.keys(SCOPE_KIND) as ScopeKey[]
|
|
@@ -347,7 +340,7 @@ function createEmptyIndex(): ResolvedOntologyIndex {
|
|
|
347
340
|
valueTypes: {},
|
|
348
341
|
sharedProperties: {},
|
|
349
342
|
groups: {},
|
|
350
|
-
|
|
343
|
+
endpoints: {}
|
|
351
344
|
}
|
|
352
345
|
}
|
|
353
346
|
|
|
@@ -541,7 +534,9 @@ function addLegacyActionProjections(
|
|
|
541
534
|
label: action.label,
|
|
542
535
|
description: action.description,
|
|
543
536
|
ownerSystemId,
|
|
544
|
-
actsOn: action.affects
|
|
537
|
+
actsOn: action.affects
|
|
538
|
+
?.map((entityId) => (entities[entityId] ? legacyObjectId(entities[entityId]) : undefined))
|
|
539
|
+
.filter((id): id is OntologyId => id !== undefined),
|
|
545
540
|
...(action.resourceId !== undefined ? { resourceId: action.resourceId } : {}),
|
|
546
541
|
...(action.invocations !== undefined ? { invocations: action.invocations } : {}),
|
|
547
542
|
...(action.lifecycle !== undefined ? { lifecycle: action.lifecycle } : {}),
|
|
@@ -43,17 +43,18 @@ Edge kinds:
|
|
|
43
43
|
|
|
44
44
|
- `contains`
|
|
45
45
|
- `references`
|
|
46
|
-
- `maps_to`
|
|
47
|
-
- `uses`
|
|
48
|
-
- `governs`
|
|
49
|
-
- `
|
|
50
|
-
- `
|
|
51
|
-
- `
|
|
52
|
-
- `emits`
|
|
46
|
+
- `maps_to`
|
|
47
|
+
- `uses`
|
|
48
|
+
- `governs`
|
|
49
|
+
- `links`
|
|
50
|
+
- `affects`
|
|
51
|
+
- `emits`
|
|
53
52
|
- `originates_from`
|
|
54
53
|
- `triggers`
|
|
55
54
|
- `applies_to`
|
|
56
|
-
- `effects`
|
|
55
|
+
- `effects`
|
|
56
|
+
|
|
57
|
+
`governed-by` is a Knowledge Graph route verb for traversing incoming `governs` edges; it is not an Organization Graph edge kind.
|
|
57
58
|
|
|
58
59
|
System nodes come from the id-keyed `OrganizationModel.systems` map. Their graph IDs use `system:<id>`, such as `system:sales.crm`.
|
|
59
60
|
|
|
@@ -11,7 +11,7 @@ export {
|
|
|
11
11
|
OntologyObjectTypeSchema,
|
|
12
12
|
OntologyScopeSchema,
|
|
13
13
|
OntologySharedPropertySchema,
|
|
14
|
-
|
|
14
|
+
OntologyEndpointTypeSchema,
|
|
15
15
|
OntologyValueTypeSchema,
|
|
16
16
|
compileOrganizationOntology,
|
|
17
17
|
formatOntologyId,
|
|
@@ -246,7 +246,13 @@ export { defineOrganizationModel, resolveOrganizationModel, resolveOrganizationM
|
|
|
246
246
|
export type { ResolvedSystemEntry, ResolvedOrganizationModel } from './resolve'
|
|
247
247
|
export { createFoundationOrganizationModel } from './foundation'
|
|
248
248
|
export { scaffoldOrganizationModel } from './scaffolders'
|
|
249
|
-
export {
|
|
249
|
+
export {
|
|
250
|
+
defineDomainRecord,
|
|
251
|
+
getClientProfile,
|
|
252
|
+
getClientProfileBySlug,
|
|
253
|
+
listAllSystems,
|
|
254
|
+
listClientProfiles
|
|
255
|
+
} from './helpers'
|
|
250
256
|
export { projectOrganizationSurfaces, validateOrganizationSurfaceProjection } from './surface-projection'
|
|
251
257
|
export type {
|
|
252
258
|
BaseOmScaffoldSpec,
|
|
@@ -282,7 +288,7 @@ export type {
|
|
|
282
288
|
OntologyRecordOrigin,
|
|
283
289
|
OntologyScope,
|
|
284
290
|
OntologySharedProperty,
|
|
285
|
-
|
|
291
|
+
OntologyEndpointType,
|
|
286
292
|
OntologyValueType,
|
|
287
293
|
ParsedOntologyId,
|
|
288
294
|
ResolvedOntologyIndex,
|
|
@@ -79,13 +79,15 @@ function pruneFlatSystemDescendantCollisions(
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
function deepMerge<T>(base: T, override: DeepPartial<T> | undefined): T {
|
|
82
|
-
if (override === undefined) {
|
|
83
|
-
return base
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
if (override === undefined) {
|
|
83
|
+
return base
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Arrays are override-replaced, not concatenated. Flattened record domains get
|
|
87
|
+
// additive-by-id object merging; see flatten-additive-merge.test.ts.
|
|
88
|
+
if (Array.isArray(base)) {
|
|
89
|
+
return (override as T) ?? base
|
|
90
|
+
}
|
|
89
91
|
|
|
90
92
|
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
91
93
|
return (override as T) ?? base
|
|
@@ -13,6 +13,7 @@ export function scaffoldKnowledgeNode(model: OrganizationModel, spec: KnowledgeN
|
|
|
13
13
|
const kind = spec.kind ?? 'reference'
|
|
14
14
|
const links = spec.systemPath === undefined ? '' : `links:\n - system:${spec.systemPath}\n`
|
|
15
15
|
const ownerIds = spec.ownerRoleId === undefined ? '' : `ownerIds:\n - ${spec.ownerRoleId}\n`
|
|
16
|
+
// Non-System scaffolds only create linked projects when explicitly requested.
|
|
16
17
|
|
|
17
18
|
return {
|
|
18
19
|
intent: 'knowledge',
|
|
@@ -2,12 +2,39 @@ import type { OrganizationModel } from '..'
|
|
|
2
2
|
import { assertSystemExists, makeOntologyId, ontologyMapName, titleize } from './helpers'
|
|
3
3
|
import type { OmScaffoldPlan, OntologyRecordScaffoldSpec } from './types'
|
|
4
4
|
|
|
5
|
+
function kindSpecificFields(spec: OntologyRecordScaffoldSpec): Record<string, unknown> {
|
|
6
|
+
if (spec.kind === 'link') {
|
|
7
|
+
return {
|
|
8
|
+
from: makeOntologyId(spec.systemPath, 'object', 'source-object'),
|
|
9
|
+
to: makeOntologyId(spec.systemPath, 'object', 'target-object')
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (spec.kind === 'action') {
|
|
14
|
+
return { actsOn: [] }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (spec.kind === 'group') {
|
|
18
|
+
return { members: [] }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {}
|
|
22
|
+
}
|
|
23
|
+
|
|
5
24
|
export function scaffoldOntologyRecord(model: OrganizationModel, spec: OntologyRecordScaffoldSpec): OmScaffoldPlan {
|
|
6
25
|
assertSystemExists(model, spec.systemPath)
|
|
7
26
|
const localId = spec.localId ?? spec.id
|
|
8
27
|
const ontologyId = makeOntologyId(spec.systemPath, spec.kind, localId)
|
|
9
28
|
const label = spec.label ?? titleize(localId)
|
|
10
29
|
const mapName = ontologyMapName(spec.kind)
|
|
30
|
+
const snippetRecord = {
|
|
31
|
+
id: ontologyId,
|
|
32
|
+
label,
|
|
33
|
+
ownerSystemId: spec.systemPath,
|
|
34
|
+
...(spec.description === undefined ? {} : { description: spec.description }),
|
|
35
|
+
...kindSpecificFields(spec)
|
|
36
|
+
}
|
|
37
|
+
// Non-System scaffolds only create linked projects when explicitly requested.
|
|
11
38
|
|
|
12
39
|
return {
|
|
13
40
|
intent: 'ontology',
|
|
@@ -18,12 +45,7 @@ export function scaffoldOntologyRecord(model: OrganizationModel, spec: OntologyR
|
|
|
18
45
|
{
|
|
19
46
|
path: 'packages/elevasis-core/src/organization-model/systems.ts',
|
|
20
47
|
description: `Add this record under ${spec.systemPath}.ontology.${mapName}.`,
|
|
21
|
-
snippet: `${JSON.stringify(ontologyId)}: {
|
|
22
|
-
id: ${JSON.stringify(ontologyId)},
|
|
23
|
-
label: ${JSON.stringify(label)},
|
|
24
|
-
ownerSystemId: ${JSON.stringify(spec.systemPath)}${spec.description === undefined ? '' : `,
|
|
25
|
-
description: ${JSON.stringify(spec.description)}`}
|
|
26
|
-
}`
|
|
48
|
+
snippet: `${JSON.stringify(ontologyId)}: ${JSON.stringify(snippetRecord, null, 2)}`
|
|
27
49
|
}
|
|
28
50
|
],
|
|
29
51
|
warnings: [],
|
|
@@ -11,6 +11,7 @@ export function scaffoldResource(model: OrganizationModel, spec: ResourceScaffol
|
|
|
11
11
|
const label = spec.label ?? titleize(spec.id)
|
|
12
12
|
const kind = spec.kind ?? 'workflow'
|
|
13
13
|
const slug = slugify(spec.id)
|
|
14
|
+
// Non-System scaffolds only create linked projects when explicitly requested.
|
|
14
15
|
const writes = spec.withStubWorkflow
|
|
15
16
|
? [
|
|
16
17
|
{
|
|
@@ -22,6 +22,7 @@ export function scaffoldSystem(model: OrganizationModel, spec: SystemScaffoldSpe
|
|
|
22
22
|
const featureSlug = slugify(systemPath.replaceAll('.', '-'))
|
|
23
23
|
const knowledgeId = `knowledge.${featureSlug}-system-overview`
|
|
24
24
|
const order = nextSystemOrder(model, parentPath)
|
|
25
|
+
// Systems default to opening a linked project because they usually start a multi-file build chain.
|
|
25
26
|
const withProject = spec.withProject ?? !spec.noProject
|
|
26
27
|
|
|
27
28
|
const systemEntry = ` ${JSON.stringify(localId)}: {
|
|
@@ -53,7 +54,7 @@ export function scaffoldSystem(model: OrganizationModel, spec: SystemScaffoldSpe
|
|
|
53
54
|
path: `packages/ui/src/features/${featureSlug}/manifest.stub.ts`,
|
|
54
55
|
mode: 'create',
|
|
55
56
|
description: 'SystemModule manifest stub. Route files are intentionally not generated.',
|
|
56
|
-
content: `import type { SystemModule } from '@repo/ui'\n\nexport const ${featureSlug.replaceAll('-', '_')}Manifest: SystemModule = {\n
|
|
57
|
+
content: `import type { SystemModule } from '@repo/ui'\n\nexport const ${featureSlug.replaceAll('-', '_')}Manifest: SystemModule = {\n key: ${JSON.stringify(featureSlug)},\n systemId: ${JSON.stringify(systemPath)}\n}\n`
|
|
57
58
|
},
|
|
58
59
|
{
|
|
59
60
|
path: `packages/elevasis-core/src/knowledge/nodes/${featureSlug}-system-overview.mdx.stub`,
|
|
@@ -3,8 +3,8 @@ import type { SidebarNode } from './domains/navigation'
|
|
|
3
3
|
import { ContractRefSchema } from './domains/resources'
|
|
4
4
|
import type { SystemEntry } from './domains/systems'
|
|
5
5
|
import type { OmTopologyNodeRef } from './domains/topology'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { buildOmCompilationContext, knowledgeTargetExists, ONTOLOGY_REFERENCE_KEY_KINDS } from './cross-ref'
|
|
7
|
+
import { listResolvedOntologyRecords, type OntologyKind } from './ontology'
|
|
8
8
|
import type { OrganizationModel } from './types'
|
|
9
9
|
|
|
10
10
|
function addIssue(ctx: z.RefinementCtx, path: Array<string | number>, message: string): void {
|
|
@@ -403,10 +403,8 @@ export function refineOrganizationModel(model: OrganizationModel, ctx: z.Refinem
|
|
|
403
403
|
})
|
|
404
404
|
})
|
|
405
405
|
// Shared cross-reference index — single source of truth for (kind, id) resolution.
|
|
406
|
-
const idx =
|
|
406
|
+
const { crossRefIndex: idx, ontologyCompilation } = buildOmCompilationContext(model)
|
|
407
407
|
const { ontologyIndexByKind, ontologyIds } = idx
|
|
408
|
-
// ontologyCompilation retained locally for listResolvedOntologyRecords and diagnostics.
|
|
409
|
-
const ontologyCompilation = compileOrganizationOntology(model)
|
|
410
408
|
|
|
411
409
|
function topologyTargetExists(ref: OmTopologyNodeRef): boolean {
|
|
412
410
|
if (ref.kind === 'system') return systemsById.has(ref.id)
|