@elevasis/core 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3117 -2166
- package/dist/index.js +574 -16
- package/dist/knowledge/index.d.ts +122 -7
- package/dist/organization-model/index.d.ts +3117 -2166
- package/dist/organization-model/index.js +574 -16
- package/dist/test-utils/index.d.ts +135 -45
- package/dist/test-utils/index.js +122 -14
- package/package.json +3 -3
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +139 -101
- package/src/execution/engine/llm/adapters/__tests__/openrouter.integration.test.ts +10 -10
- package/src/execution/engine/workflow/types.ts +5 -7
- package/src/knowledge/__tests__/queries.test.ts +960 -546
- package/src/knowledge/format.ts +322 -100
- package/src/knowledge/index.ts +18 -5
- package/src/knowledge/queries.ts +1004 -239
- package/src/organization-model/__tests__/deprecate-helpers.test.ts +71 -0
- package/src/organization-model/__tests__/domains/resources.test.ts +19 -8
- package/src/organization-model/__tests__/domains/topology.test.ts +188 -0
- package/src/organization-model/__tests__/graph.test.ts +98 -7
- package/src/organization-model/__tests__/resolve.test.ts +9 -7
- package/src/organization-model/__tests__/scaffolders.test.ts +93 -0
- package/src/organization-model/__tests__/schema.test.ts +14 -4
- package/src/organization-model/defaults.ts +5 -3
- package/src/organization-model/domains/resources.ts +63 -20
- package/src/organization-model/domains/topology.ts +261 -0
- package/src/organization-model/graph/build.ts +63 -15
- package/src/organization-model/graph/schema.ts +4 -3
- package/src/organization-model/graph/types.ts +5 -4
- package/src/organization-model/helpers.ts +76 -9
- package/src/organization-model/icons.ts +1 -0
- package/src/organization-model/index.ts +7 -5
- package/src/organization-model/ontology.ts +2 -5
- package/src/organization-model/organization-model.mdx +16 -11
- package/src/organization-model/published.ts +51 -15
- package/src/organization-model/scaffolders/helpers.ts +84 -0
- package/src/organization-model/scaffolders/index.ts +19 -0
- package/src/organization-model/scaffolders/scaffoldKnowledgeNode.ts +48 -0
- package/src/organization-model/scaffolders/scaffoldOntologyRecord.ts +38 -0
- package/src/organization-model/scaffolders/scaffoldResource.ts +59 -0
- package/src/organization-model/scaffolders/scaffoldSystem.ts +110 -0
- package/src/organization-model/scaffolders/types.ts +81 -0
- package/src/organization-model/schema.ts +51 -11
- package/src/organization-model/types.ts +25 -11
- package/src/platform/constants/versions.ts +1 -1
- package/src/platform/registry/__tests__/validation.test.ts +199 -14
- package/src/platform/registry/resource-registry.ts +11 -11
- package/src/platform/registry/validation.ts +226 -34
- package/src/reference/_generated/contracts.md +139 -101
- package/src/reference/glossary.md +74 -72
- package/src/supabase/database.types.ts +3156 -3153
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { ModelIdSchema } from './shared'
|
|
2
|
+
import { DescriptionSchema, LabelSchema, ModelIdSchema } from './shared'
|
|
3
3
|
import { SystemPathSchema, SystemLifecycleSchema } from './systems'
|
|
4
4
|
import { ActionInvocationSchema } from './actions'
|
|
5
5
|
import { OntologyIdSchema } from '../ontology'
|
|
@@ -57,13 +57,35 @@ export const EventDescriptorSchema = EventEmissionDescriptorSchema.extend({
|
|
|
57
57
|
ownerKind: z.enum(['resource', 'entity']).meta({ label: 'Owner kind' })
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
-
export const ResourceOntologyBindingSchema = z
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
export const ResourceOntologyBindingSchema = z
|
|
61
|
+
.object({
|
|
62
|
+
actions: z.array(OntologyIdSchema).optional(),
|
|
63
|
+
primaryAction: OntologyIdSchema.optional(),
|
|
64
|
+
reads: z.array(OntologyIdSchema).optional(),
|
|
65
|
+
writes: z.array(OntologyIdSchema).optional(),
|
|
66
|
+
usesCatalogs: z.array(OntologyIdSchema).optional(),
|
|
67
|
+
emits: z.array(OntologyIdSchema).optional()
|
|
68
|
+
})
|
|
69
|
+
.superRefine((binding, ctx) => {
|
|
70
|
+
if (binding.primaryAction === undefined) return
|
|
71
|
+
if (binding.actions?.includes(binding.primaryAction)) return
|
|
72
|
+
|
|
73
|
+
ctx.addIssue({
|
|
74
|
+
code: z.ZodIssueCode.custom,
|
|
75
|
+
path: ['primaryAction'],
|
|
76
|
+
message: 'Resource ontology primaryAction must be included in actions'
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
type OntologyRefInput = string | { id: string }
|
|
81
|
+
type ResourceOntologyBindingInput = {
|
|
82
|
+
actions?: OntologyRefInput[]
|
|
83
|
+
primaryAction?: OntologyRefInput
|
|
84
|
+
reads?: OntologyRefInput[]
|
|
85
|
+
writes?: OntologyRefInput[]
|
|
86
|
+
usesCatalogs?: OntologyRefInput[]
|
|
87
|
+
emits?: OntologyRefInput[]
|
|
88
|
+
}
|
|
67
89
|
|
|
68
90
|
export const CodeReferenceSchema = z.object({
|
|
69
91
|
path: z
|
|
@@ -83,7 +105,11 @@ const ResourceEntryBaseSchema = z.object({
|
|
|
83
105
|
/** Domain-map iteration order. Convention: multiples of 10 (10, 20, 30, ...) to allow easy insertion. */
|
|
84
106
|
order: z.number().default(0),
|
|
85
107
|
/** Required single System membership — value is a dot-separated system path (e.g. "sales.lead-gen"). */
|
|
86
|
-
systemPath: SystemPathSchema.meta({ ref: 'system' }),
|
|
108
|
+
systemPath: SystemPathSchema.meta({ ref: 'system' }),
|
|
109
|
+
/** Executable display title owned by the OM Resource descriptor. */
|
|
110
|
+
title: LabelSchema.optional(),
|
|
111
|
+
/** Executable display description owned by the OM Resource descriptor. */
|
|
112
|
+
description: DescriptionSchema.optional(),
|
|
87
113
|
/** Optional role responsible for maintaining this resource. */
|
|
88
114
|
ownerRoleId: ModelIdSchema.meta({ ref: 'role' }).optional(),
|
|
89
115
|
status: ResourceGovernanceStatusSchema,
|
|
@@ -97,12 +123,10 @@ const ResourceEntryBaseSchema = z.object({
|
|
|
97
123
|
codeRefs: z.array(CodeReferenceSchema).default([])
|
|
98
124
|
})
|
|
99
125
|
|
|
100
|
-
export const WorkflowResourceEntrySchema = ResourceEntryBaseSchema.extend({
|
|
101
|
-
kind: z.literal('workflow'),
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
emits: z.array(EventEmissionDescriptorSchema).optional()
|
|
105
|
-
})
|
|
126
|
+
export const WorkflowResourceEntrySchema = ResourceEntryBaseSchema.extend({
|
|
127
|
+
kind: z.literal('workflow'),
|
|
128
|
+
emits: z.array(EventEmissionDescriptorSchema).optional()
|
|
129
|
+
})
|
|
106
130
|
|
|
107
131
|
export const AgentResourceEntrySchema = ResourceEntryBaseSchema.extend({
|
|
108
132
|
kind: z.literal('agent'),
|
|
@@ -155,13 +179,32 @@ export function defineResource<const TResource extends ResourceEntry>(resource:
|
|
|
155
179
|
return ResourceEntrySchema.parse(resource) as TResource
|
|
156
180
|
}
|
|
157
181
|
|
|
158
|
-
export function defineResources<const TResources extends Record<string, ResourceEntry>>(
|
|
159
|
-
resources: TResources
|
|
160
|
-
): TResources {
|
|
182
|
+
export function defineResources<const TResources extends Record<string, ResourceEntry>>(
|
|
183
|
+
resources: TResources
|
|
184
|
+
): TResources {
|
|
161
185
|
return Object.fromEntries(
|
|
162
186
|
Object.entries(resources).map(([key, resource]) => [key, ResourceEntrySchema.parse(resource)])
|
|
163
|
-
) as TResources
|
|
164
|
-
}
|
|
187
|
+
) as TResources
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function ontologyIdFrom(input: OntologyRefInput): string {
|
|
191
|
+
return typeof input === 'string' ? input : input.id
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function ontologyIdArrayFrom(input: OntologyRefInput[] | undefined): string[] | undefined {
|
|
195
|
+
return input?.map(ontologyIdFrom)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function defineResourceOntology(input: ResourceOntologyBindingInput): ResourceOntologyBinding {
|
|
199
|
+
return ResourceOntologyBindingSchema.parse({
|
|
200
|
+
actions: ontologyIdArrayFrom(input.actions),
|
|
201
|
+
primaryAction: input.primaryAction === undefined ? undefined : ontologyIdFrom(input.primaryAction),
|
|
202
|
+
reads: ontologyIdArrayFrom(input.reads),
|
|
203
|
+
writes: ontologyIdArrayFrom(input.writes),
|
|
204
|
+
usesCatalogs: ontologyIdArrayFrom(input.usesCatalogs),
|
|
205
|
+
emits: ontologyIdArrayFrom(input.emits)
|
|
206
|
+
})
|
|
207
|
+
}
|
|
165
208
|
|
|
166
209
|
export type ResourceId = z.infer<typeof ResourceIdSchema>
|
|
167
210
|
export type ResourceKind = z.infer<typeof ResourceKindSchema>
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { OntologyIdSchema, parseOntologyId } from '../ontology'
|
|
3
|
+
import { JsonValueSchema, SystemPathSchema } from './systems'
|
|
4
|
+
import { ModelIdSchema } from './shared'
|
|
5
|
+
import { ResourceIdSchema, type ResourceEntry } from './resources'
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Topology domain
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
//
|
|
11
|
+
// Topology captures durable operational wiring. It stays separate from
|
|
12
|
+
// ontology, which describes semantic meaning.
|
|
13
|
+
|
|
14
|
+
const SecretLikeMetadataKeySchema = /(?:secret|password|passwd|token|api[-_]?key|credential|private[-_]?key)/i
|
|
15
|
+
const SecretLikeMetadataValueSchema =
|
|
16
|
+
/(?:sk-[A-Za-z0-9_-]{12,}|pk_live_[A-Za-z0-9_-]{12,}|eyJ[A-Za-z0-9_-]{20,}|-----BEGIN (?:RSA |OPENSSH |EC )?PRIVATE KEY-----)/
|
|
17
|
+
|
|
18
|
+
export const OmTopologyNodeKindSchema = z.enum([
|
|
19
|
+
'system',
|
|
20
|
+
'resource',
|
|
21
|
+
'ontology',
|
|
22
|
+
'policy',
|
|
23
|
+
'role',
|
|
24
|
+
'trigger',
|
|
25
|
+
'humanCheckpoint',
|
|
26
|
+
'externalResource'
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
export const OmTopologyRelationshipKindSchema = z.enum(['triggers', 'uses', 'approval'])
|
|
30
|
+
|
|
31
|
+
export const OmTopologyNodeRefSchema = z.discriminatedUnion('kind', [
|
|
32
|
+
z.object({ kind: z.literal('system'), id: ModelIdSchema }),
|
|
33
|
+
z.object({ kind: z.literal('resource'), id: ResourceIdSchema }),
|
|
34
|
+
z.object({ kind: z.literal('ontology'), id: OntologyIdSchema }),
|
|
35
|
+
z.object({ kind: z.literal('policy'), id: ModelIdSchema }),
|
|
36
|
+
z.object({ kind: z.literal('role'), id: ModelIdSchema }),
|
|
37
|
+
z.object({ kind: z.literal('trigger'), id: ResourceIdSchema }),
|
|
38
|
+
z.object({ kind: z.literal('humanCheckpoint'), id: ResourceIdSchema }),
|
|
39
|
+
z.object({ kind: z.literal('externalResource'), id: ResourceIdSchema })
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
export const OmTopologyMetadataSchema = z
|
|
43
|
+
.record(z.string().trim().min(1).max(120), JsonValueSchema)
|
|
44
|
+
.superRefine((metadata, ctx) => {
|
|
45
|
+
function visit(value: unknown, path: Array<string | number>): void {
|
|
46
|
+
if (typeof value === 'string' && SecretLikeMetadataValueSchema.test(value)) {
|
|
47
|
+
ctx.addIssue({
|
|
48
|
+
code: z.ZodIssueCode.custom,
|
|
49
|
+
path,
|
|
50
|
+
message: 'Topology metadata must not contain secret-like values'
|
|
51
|
+
})
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
value.forEach((entry, index) => visit(entry, [...path, index]))
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof value !== 'object' || value === null) return
|
|
61
|
+
|
|
62
|
+
Object.entries(value).forEach(([key, entry]) => {
|
|
63
|
+
if (SecretLikeMetadataKeySchema.test(key)) {
|
|
64
|
+
ctx.addIssue({
|
|
65
|
+
code: z.ZodIssueCode.custom,
|
|
66
|
+
path: [...path, key],
|
|
67
|
+
message: `Topology metadata key "${key}" looks secret-like`
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
visit(entry, [...path, key])
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
visit(metadata, [])
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
export const OmTopologyRelationshipSchema = z.object({
|
|
78
|
+
from: OmTopologyNodeRefSchema,
|
|
79
|
+
kind: OmTopologyRelationshipKindSchema,
|
|
80
|
+
to: OmTopologyNodeRefSchema,
|
|
81
|
+
systemPath: SystemPathSchema.optional(),
|
|
82
|
+
required: z.boolean().optional(),
|
|
83
|
+
metadata: OmTopologyMetadataSchema.optional()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
export const OmTopologyDomainSchema = z
|
|
87
|
+
.object({
|
|
88
|
+
version: z.literal(1).default(1),
|
|
89
|
+
relationships: z.record(z.string().trim().min(1).max(255), OmTopologyRelationshipSchema).default({})
|
|
90
|
+
})
|
|
91
|
+
.default({ version: 1, relationships: {} })
|
|
92
|
+
|
|
93
|
+
export const DEFAULT_ORGANIZATION_MODEL_TOPOLOGY: z.infer<typeof OmTopologyDomainSchema> = {
|
|
94
|
+
version: 1,
|
|
95
|
+
relationships: {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type OmTopologyNodeKind = z.infer<typeof OmTopologyNodeKindSchema>
|
|
99
|
+
export type OmTopologyRelationshipKind = z.infer<typeof OmTopologyRelationshipKindSchema>
|
|
100
|
+
export type OmTopologyNodeRef = z.infer<typeof OmTopologyNodeRefSchema>
|
|
101
|
+
export type OmTopologyMetadata = z.infer<typeof OmTopologyMetadataSchema>
|
|
102
|
+
export type OmTopologyRelationship = z.infer<typeof OmTopologyRelationshipSchema>
|
|
103
|
+
export type OmTopologyDomain = z.infer<typeof OmTopologyDomainSchema>
|
|
104
|
+
|
|
105
|
+
type TypedIdObject = { id: string }
|
|
106
|
+
type TopologyRefFactoryInput = string | TypedIdObject
|
|
107
|
+
type TopologyRelationshipInput = Omit<OmTopologyRelationship, 'from' | 'to'> & {
|
|
108
|
+
from: OmTopologyNodeInput
|
|
109
|
+
to: OmTopologyNodeInput
|
|
110
|
+
}
|
|
111
|
+
type TopologyRelationshipOptions = Omit<Partial<OmTopologyRelationship>, 'from' | 'kind' | 'to'>
|
|
112
|
+
export type OmTopologyNodeInput = OmTopologyNodeRef | ResourceEntry
|
|
113
|
+
|
|
114
|
+
function idFrom(input: TopologyRefFactoryInput): string {
|
|
115
|
+
return typeof input === 'string' ? input : input.id
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parseRef(kind: OmTopologyNodeKind, id: string): OmTopologyNodeRef {
|
|
119
|
+
return OmTopologyNodeRefSchema.parse({ kind, id })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isNodeRef(input: unknown): input is OmTopologyNodeRef {
|
|
123
|
+
return OmTopologyNodeRefSchema.safeParse(input).success
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isResourceEntry(input: unknown): input is ResourceEntry {
|
|
127
|
+
if (typeof input !== 'object' || input === null) return false
|
|
128
|
+
const candidate = input as Partial<ResourceEntry>
|
|
129
|
+
return (
|
|
130
|
+
typeof candidate.id === 'string' &&
|
|
131
|
+
typeof candidate.systemPath === 'string' &&
|
|
132
|
+
typeof candidate.status === 'string' &&
|
|
133
|
+
['workflow', 'agent', 'integration', 'script'].includes(String(candidate.kind))
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const topologyRef = {
|
|
138
|
+
system: (system: TopologyRefFactoryInput) => parseRef('system', idFrom(system)),
|
|
139
|
+
resource: (resource: TopologyRefFactoryInput) => parseRef('resource', idFrom(resource)),
|
|
140
|
+
ontology: (record: TopologyRefFactoryInput) => parseRef('ontology', idFrom(record)),
|
|
141
|
+
policy: (policy: TopologyRefFactoryInput) => parseRef('policy', idFrom(policy)),
|
|
142
|
+
role: (role: TopologyRefFactoryInput) => parseRef('role', idFrom(role)),
|
|
143
|
+
trigger: (trigger: TopologyRefFactoryInput) => parseRef('trigger', idFrom(trigger)),
|
|
144
|
+
humanCheckpoint: (checkpoint: TopologyRefFactoryInput) => parseRef('humanCheckpoint', idFrom(checkpoint)),
|
|
145
|
+
externalResource: (externalResource: TopologyRefFactoryInput) => parseRef('externalResource', idFrom(externalResource))
|
|
146
|
+
} as const
|
|
147
|
+
|
|
148
|
+
export const topologyRelationship = {
|
|
149
|
+
triggers: (from: OmTopologyNodeInput, to: OmTopologyNodeInput, options: TopologyRelationshipOptions = {}) =>
|
|
150
|
+
defineTopologyRelationship({
|
|
151
|
+
...options,
|
|
152
|
+
from,
|
|
153
|
+
kind: 'triggers',
|
|
154
|
+
to
|
|
155
|
+
}),
|
|
156
|
+
uses: (from: OmTopologyNodeInput, to: OmTopologyNodeInput, options: TopologyRelationshipOptions = {}) =>
|
|
157
|
+
defineTopologyRelationship({
|
|
158
|
+
...options,
|
|
159
|
+
from,
|
|
160
|
+
kind: 'uses',
|
|
161
|
+
to
|
|
162
|
+
}),
|
|
163
|
+
approval: (from: OmTopologyNodeInput, to: OmTopologyNodeInput, options: TopologyRelationshipOptions = {}) =>
|
|
164
|
+
defineTopologyRelationship({
|
|
165
|
+
...options,
|
|
166
|
+
from,
|
|
167
|
+
kind: 'approval',
|
|
168
|
+
to
|
|
169
|
+
}),
|
|
170
|
+
usesIntegration: (
|
|
171
|
+
from: OmTopologyNodeInput,
|
|
172
|
+
integration: OmTopologyNodeInput,
|
|
173
|
+
options: TopologyRelationshipOptions = {}
|
|
174
|
+
) =>
|
|
175
|
+
defineTopologyRelationship({
|
|
176
|
+
required: true,
|
|
177
|
+
...options,
|
|
178
|
+
from,
|
|
179
|
+
kind: 'uses',
|
|
180
|
+
to: integration
|
|
181
|
+
}),
|
|
182
|
+
requestsApproval: (
|
|
183
|
+
from: OmTopologyNodeInput,
|
|
184
|
+
checkpoint: TopologyRefFactoryInput,
|
|
185
|
+
options: TopologyRelationshipOptions = {}
|
|
186
|
+
) =>
|
|
187
|
+
defineTopologyRelationship({
|
|
188
|
+
required: true,
|
|
189
|
+
...options,
|
|
190
|
+
from,
|
|
191
|
+
kind: 'approval',
|
|
192
|
+
to: topologyRef.humanCheckpoint(checkpoint)
|
|
193
|
+
}),
|
|
194
|
+
checkpointRoutesTo: (
|
|
195
|
+
checkpoint: TopologyRefFactoryInput,
|
|
196
|
+
to: OmTopologyNodeInput,
|
|
197
|
+
options: TopologyRelationshipOptions = {}
|
|
198
|
+
) =>
|
|
199
|
+
defineTopologyRelationship({
|
|
200
|
+
required: true,
|
|
201
|
+
...options,
|
|
202
|
+
from: topologyRef.humanCheckpoint(checkpoint),
|
|
203
|
+
kind: 'triggers',
|
|
204
|
+
to
|
|
205
|
+
})
|
|
206
|
+
} as const
|
|
207
|
+
|
|
208
|
+
export function compileTopologyNodeRef(input: OmTopologyNodeInput): OmTopologyNodeRef {
|
|
209
|
+
if (isNodeRef(input)) return input
|
|
210
|
+
if (isResourceEntry(input)) return topologyRef.resource(input)
|
|
211
|
+
|
|
212
|
+
throw new Error('Topology node refs must be typed node objects or serializable { kind, id } refs')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function parseTopologyNodeRef(input: string | OmTopologyNodeRef): OmTopologyNodeRef {
|
|
216
|
+
if (typeof input !== 'string') return OmTopologyNodeRefSchema.parse(input)
|
|
217
|
+
|
|
218
|
+
const separatorIndex = input.indexOf(':')
|
|
219
|
+
if (separatorIndex === -1) {
|
|
220
|
+
throw new Error(`Topology node ref "${input}" must use <kind>:<id>`)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const kind = input.slice(0, separatorIndex)
|
|
224
|
+
const id = input.slice(separatorIndex + 1)
|
|
225
|
+
if (!OmTopologyNodeKindSchema.safeParse(kind).success) {
|
|
226
|
+
throw new Error(`Topology node ref "${input}" has unsupported kind "${kind}"`)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return OmTopologyNodeRefSchema.parse({ kind, id })
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function defineTopologyRelationship(input: TopologyRelationshipInput): OmTopologyRelationship {
|
|
233
|
+
return OmTopologyRelationshipSchema.parse({
|
|
234
|
+
...input,
|
|
235
|
+
from: compileTopologyNodeRef(input.from),
|
|
236
|
+
to: compileTopologyNodeRef(input.to)
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function defineTopology(
|
|
241
|
+
relationships: Record<string, TopologyRelationshipInput> | TopologyRelationshipInput[]
|
|
242
|
+
): OmTopologyDomain {
|
|
243
|
+
const entries = Array.isArray(relationships)
|
|
244
|
+
? relationships.map((relationship, index) => [`relationship-${index + 1}`, relationship] as const)
|
|
245
|
+
: Object.entries(relationships)
|
|
246
|
+
|
|
247
|
+
return OmTopologyDomainSchema.parse({
|
|
248
|
+
version: 1,
|
|
249
|
+
relationships: Object.fromEntries(entries.map(([key, relationship]) => [key, defineTopologyRelationship(relationship)]))
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function isOntologyTopologyRef(value: unknown): value is Extract<OmTopologyNodeRef, { kind: 'ontology' }> {
|
|
254
|
+
if (!isNodeRef(value) || value.kind !== 'ontology') return false
|
|
255
|
+
try {
|
|
256
|
+
parseOntologyId(value.id)
|
|
257
|
+
return true
|
|
258
|
+
} catch {
|
|
259
|
+
return false
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -8,10 +8,11 @@ import type {
|
|
|
8
8
|
OrganizationGraphNode,
|
|
9
9
|
OrganizationGraphNodeKind
|
|
10
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 {
|
|
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'
|
|
15
16
|
import {
|
|
16
17
|
getAllPipelines,
|
|
17
18
|
getAllBuildTemplates,
|
|
@@ -286,8 +287,40 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
286
287
|
systemPathByRef.set(path, path)
|
|
287
288
|
systemPathByRef.set(system.id, path)
|
|
288
289
|
}
|
|
289
|
-
const validSystemRefs = new Set(systemPathByRef.keys())
|
|
290
|
-
const systemNodeId = (systemRef: string) => nodeId('system', systemPathByRef.get(systemRef) ?? systemRef)
|
|
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
|
+
}
|
|
291
324
|
|
|
292
325
|
for (const { path, system } of systemsWithPaths.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
293
326
|
const id = nodeId('system', path)
|
|
@@ -649,7 +682,7 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
649
682
|
})
|
|
650
683
|
}
|
|
651
684
|
|
|
652
|
-
pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, '
|
|
685
|
+
pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'actions', resource.ontology?.actions)
|
|
653
686
|
pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'reads', resource.ontology?.reads)
|
|
654
687
|
pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'writes', resource.ontology?.writes)
|
|
655
688
|
pushOntologyBindingEdges(edges, edgeIds, resourceNode.id, 'uses_catalog', resource.ontology?.usesCatalogs)
|
|
@@ -698,9 +731,9 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
698
731
|
}
|
|
699
732
|
}
|
|
700
733
|
|
|
701
|
-
for (const policy of Object.values(organizationModel.policies).sort(
|
|
702
|
-
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
703
|
-
)) {
|
|
734
|
+
for (const policy of Object.values(organizationModel.policies).sort(
|
|
735
|
+
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
736
|
+
)) {
|
|
704
737
|
const id = nodeId('policy', policy.id)
|
|
705
738
|
pushUniqueNode(nodes, nodeIds, {
|
|
706
739
|
id,
|
|
@@ -787,11 +820,26 @@ export function buildOrganizationGraph(input: BuildOrganizationGraphInput): Orga
|
|
|
787
820
|
targetId: nodeId('role', effect.roleId),
|
|
788
821
|
label: effect.kind
|
|
789
822
|
})
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
for (const
|
|
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(
|
|
795
843
|
(a, b) => a.order - b.order || a.id.localeCompare(b.id)
|
|
796
844
|
)) {
|
|
797
845
|
const id = nodeId('customer-segment', segment.id)
|
|
@@ -33,10 +33,11 @@ export const OrganizationGraphEdgeKindSchema = z.enum([
|
|
|
33
33
|
'affects',
|
|
34
34
|
'emits',
|
|
35
35
|
'originates_from',
|
|
36
|
-
'triggers',
|
|
37
|
-
'
|
|
36
|
+
'triggers',
|
|
37
|
+
'approval',
|
|
38
|
+
'applies_to',
|
|
38
39
|
'effects',
|
|
39
|
-
'
|
|
40
|
+
'actions',
|
|
40
41
|
'reads',
|
|
41
42
|
'writes',
|
|
42
43
|
'uses_catalog'
|
|
@@ -32,11 +32,12 @@ export type OrganizationGraphEdgeKind =
|
|
|
32
32
|
| 'links'
|
|
33
33
|
| 'affects'
|
|
34
34
|
| 'emits'
|
|
35
|
-
| 'originates_from'
|
|
36
|
-
| 'triggers'
|
|
37
|
-
| '
|
|
35
|
+
| 'originates_from'
|
|
36
|
+
| 'triggers'
|
|
37
|
+
| 'approval'
|
|
38
|
+
| 'applies_to'
|
|
38
39
|
| 'effects'
|
|
39
|
-
| '
|
|
40
|
+
| 'actions'
|
|
40
41
|
| 'reads'
|
|
41
42
|
| 'writes'
|
|
42
43
|
| 'uses_catalog'
|
|
@@ -2,6 +2,7 @@ import type { OrganizationModel, OrganizationModelSystemEntry } from './types'
|
|
|
2
2
|
import type { ContentNode } from './content-kinds/types'
|
|
3
3
|
import type { JsonValue } from './domains/systems'
|
|
4
4
|
import type { ResourceEntry } from './domains/resources'
|
|
5
|
+
import type { OmTopologyRelationship } from './domains/topology'
|
|
5
6
|
|
|
6
7
|
// W1A has landed: ContentNode and SystemEntry (with content + subsystems) are now
|
|
7
8
|
// defined in their canonical locations.
|
|
@@ -314,14 +315,80 @@ export function resolveSystemConfig(model: OrganizationModel, path: string): Rec
|
|
|
314
315
|
* getResourcesForSystem(model, 'sales', { includeDescendants: true })
|
|
315
316
|
* // → resources where systemPath === 'sales' OR systemPath starts with 'sales.'
|
|
316
317
|
*/
|
|
317
|
-
export function getResourcesForSystem(
|
|
318
|
-
model: OrganizationModel,
|
|
319
|
-
systemPath: string,
|
|
320
|
-
options: { includeDescendants?: boolean } = {}
|
|
321
|
-
): ResourceEntry[] {
|
|
318
|
+
export function getResourcesForSystem(
|
|
319
|
+
model: OrganizationModel,
|
|
320
|
+
systemPath: string,
|
|
321
|
+
options: { includeDescendants?: boolean } = {}
|
|
322
|
+
): ResourceEntry[] {
|
|
322
323
|
const { includeDescendants = false } = options
|
|
323
324
|
const prefix = systemPath + '.'
|
|
324
|
-
return Object.values(model.resources ?? {}).filter(
|
|
325
|
-
(r) => r.systemPath === systemPath || (includeDescendants && r.systemPath.startsWith(prefix))
|
|
326
|
-
)
|
|
327
|
-
}
|
|
325
|
+
return Object.values(model.resources ?? {}).filter(
|
|
326
|
+
(r) => r.systemPath === systemPath || (includeDescendants && r.systemPath.startsWith(prefix))
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export interface SystemTopologyEdge {
|
|
331
|
+
id: string
|
|
332
|
+
relationship: OmTopologyRelationship
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export interface SystemDeprecationDependents {
|
|
336
|
+
resources: ResourceEntry[]
|
|
337
|
+
topologyEdges: SystemTopologyEdge[]
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function systemPathIsInScope(candidate: string, systemPath: string, includeDescendants: boolean): boolean {
|
|
341
|
+
return candidate === systemPath || (includeDescendants && candidate.startsWith(`${systemPath}.`))
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function topologyRelationshipTouchesSystem(
|
|
345
|
+
relationship: OmTopologyRelationship,
|
|
346
|
+
systemPath: string,
|
|
347
|
+
resourceIds: Set<string>,
|
|
348
|
+
includeDescendants: boolean
|
|
349
|
+
): boolean {
|
|
350
|
+
if (
|
|
351
|
+
relationship.systemPath !== undefined &&
|
|
352
|
+
systemPathIsInScope(relationship.systemPath, systemPath, includeDescendants)
|
|
353
|
+
) {
|
|
354
|
+
return true
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
for (const ref of [relationship.from, relationship.to]) {
|
|
358
|
+
if (ref.kind === 'system' && systemPathIsInScope(ref.id, systemPath, includeDescendants)) return true
|
|
359
|
+
if (ref.kind === 'resource' && resourceIds.has(ref.id)) return true
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return false
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function getTopologyEdgesForSystem(
|
|
366
|
+
model: OrganizationModel,
|
|
367
|
+
systemPath: string,
|
|
368
|
+
options: { includeDescendants?: boolean; resourceIds?: Iterable<string> } = {}
|
|
369
|
+
): SystemTopologyEdge[] {
|
|
370
|
+
const { includeDescendants = false } = options
|
|
371
|
+
const resourceIds = new Set(
|
|
372
|
+
options.resourceIds ?? getResourcesForSystem(model, systemPath, { includeDescendants }).map((r) => r.id)
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
return Object.entries(model.topology?.relationships ?? {})
|
|
376
|
+
.filter(([, relationship]) =>
|
|
377
|
+
topologyRelationshipTouchesSystem(relationship, systemPath, resourceIds, includeDescendants)
|
|
378
|
+
)
|
|
379
|
+
.map(([id, relationship]) => ({ id, relationship }))
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function getSystemDeprecationDependents(
|
|
383
|
+
model: OrganizationModel,
|
|
384
|
+
systemPath: string,
|
|
385
|
+
options: { includeDescendants?: boolean } = {}
|
|
386
|
+
): SystemDeprecationDependents {
|
|
387
|
+
const resources = getResourcesForSystem(model, systemPath, options).filter((resource) => resource.status === 'active')
|
|
388
|
+
const resourceIds = new Set(resources.map((resource) => resource.id))
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
resources,
|
|
392
|
+
topologyEdges: getTopologyEdgesForSystem(model, systemPath, { ...options, resourceIds })
|
|
393
|
+
}
|
|
394
|
+
}
|
|
@@ -13,8 +13,9 @@ export * from './resolve'
|
|
|
13
13
|
export * from './foundation'
|
|
14
14
|
export * from './surface-projection'
|
|
15
15
|
export * from './helpers'
|
|
16
|
-
export * from './migration-helpers'
|
|
17
|
-
export * from './
|
|
16
|
+
export * from './migration-helpers'
|
|
17
|
+
export * from './scaffolders'
|
|
18
|
+
export * from './graph'
|
|
18
19
|
export * from './catalogs/lead-gen'
|
|
19
20
|
export * from './domains/branding'
|
|
20
21
|
// Phase 4: OrganizationModelSalesSchema, OrganizationModelProspectingSchema,
|
|
@@ -73,9 +74,10 @@ export type {
|
|
|
73
74
|
ActionRegistry
|
|
74
75
|
} from './domains/prospecting'
|
|
75
76
|
export { ProjectsDomainStateSchema } from './domains/projects'
|
|
76
|
-
export * from './domains/systems'
|
|
77
|
-
export * from './domains/resources'
|
|
78
|
-
export
|
|
77
|
+
export * from './domains/systems'
|
|
78
|
+
export * from './domains/resources'
|
|
79
|
+
export * from './domains/topology'
|
|
80
|
+
export {
|
|
79
81
|
DEFAULT_ORGANIZATION_MODEL_NAVIGATION,
|
|
80
82
|
getSortedSidebarEntries,
|
|
81
83
|
NavigationGroupSchema,
|
|
@@ -496,7 +496,6 @@ function addLegacyEntityProjections(
|
|
|
496
496
|
}
|
|
497
497
|
}
|
|
498
498
|
: {}),
|
|
499
|
-
legacyEntityId: entity.id,
|
|
500
499
|
...(entity.rowSchema !== undefined ? { rowSchema: entity.rowSchema } : {}),
|
|
501
500
|
...(entity.stateCatalogId !== undefined ? { stateCatalogId: entity.stateCatalogId } : {})
|
|
502
501
|
}
|
|
@@ -524,8 +523,7 @@ function addLegacyEntityProjections(
|
|
|
524
523
|
from: legacyObjectId(entity),
|
|
525
524
|
to: legacyObjectId(targetEntity),
|
|
526
525
|
cardinality: link.kind,
|
|
527
|
-
...(link.via !== undefined ? { via: link.via } : {})
|
|
528
|
-
legacyEntityId: entity.id
|
|
526
|
+
...(link.via !== undefined ? { via: link.via } : {})
|
|
529
527
|
}
|
|
530
528
|
|
|
531
529
|
addRecord(index, diagnostics, sourcesById, 'linkTypes', linkType, {
|
|
@@ -606,8 +604,7 @@ function addSystemContentProjections(
|
|
|
606
604
|
? { appliesTo: formatOntologyId({ scope: systemPath, kind: 'object', localId: node.data['entityId'] }) }
|
|
607
605
|
: {}),
|
|
608
606
|
...(Object.keys(entries).length > 0 ? { entries } : {}),
|
|
609
|
-
...(node.data !== undefined ? { data: node.data } : {})
|
|
610
|
-
legacyContentId: `${systemPath}:${localId}`
|
|
607
|
+
...(node.data !== undefined ? { data: node.data } : {})
|
|
611
608
|
}
|
|
612
609
|
|
|
613
610
|
addRecord(index, diagnostics, sourcesById, 'catalogTypes', catalogType, {
|