@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.
Files changed (44) hide show
  1. package/dist/auth/index.d.ts +8 -3
  2. package/dist/auth/index.js +6 -0
  3. package/dist/business/entities-published.d.ts +1 -1
  4. package/dist/index.d.ts +12 -13
  5. package/dist/index.js +48 -29
  6. package/dist/knowledge/index.d.ts +94 -6
  7. package/dist/knowledge/index.js +172 -8
  8. package/dist/organization-model/index.d.ts +12 -13
  9. package/dist/organization-model/index.js +48 -29
  10. package/dist/test-utils/index.d.ts +5 -6
  11. package/dist/test-utils/index.js +21 -18
  12. package/package.json +3 -3
  13. package/src/auth/access-keys.ts +6 -0
  14. package/src/business/acquisition/api-schemas.ts +1 -1
  15. package/src/business/base-entities.ts +1 -1
  16. package/src/knowledge/cli-helpers.ts +211 -0
  17. package/src/knowledge/index.ts +13 -0
  18. package/src/knowledge/published.ts +18 -5
  19. package/src/knowledge/queries.ts +5 -5
  20. package/src/organization-model/__tests__/cross-ref.test.ts +11 -1
  21. package/src/organization-model/__tests__/domains/systems.test.ts +34 -8
  22. package/src/organization-model/__tests__/scaffolders.test.ts +30 -1
  23. package/src/organization-model/__tests__/schema-refinements.test.ts +178 -0
  24. package/src/organization-model/cross-ref.ts +43 -7
  25. package/src/organization-model/defaults.ts +2 -2
  26. package/src/organization-model/domains/actions.ts +1 -1
  27. package/src/organization-model/domains/resources.ts +1 -1
  28. package/src/organization-model/domains/systems.ts +0 -4
  29. package/src/organization-model/ontology.ts +13 -18
  30. package/src/organization-model/organization-graph.mdx +9 -8
  31. package/src/organization-model/published.ts +9 -3
  32. package/src/organization-model/resolve.ts +9 -7
  33. package/src/organization-model/scaffolders/helpers.ts +1 -1
  34. package/src/organization-model/scaffolders/scaffoldKnowledgeNode.ts +1 -0
  35. package/src/organization-model/scaffolders/scaffoldOntologyRecord.ts +28 -6
  36. package/src/organization-model/scaffolders/scaffoldResource.ts +1 -0
  37. package/src/organization-model/scaffolders/scaffoldSystem.ts +2 -1
  38. package/src/organization-model/schema-refinements.ts +3 -5
  39. package/src/platform/registry/__tests__/validation.test.ts +28 -0
  40. package/src/platform/registry/validation.ts +20 -2
  41. package/src/scaffold-registry/__tests__/index.test.ts +380 -206
  42. package/src/scaffold-registry/index.ts +392 -381
  43. package/src/test-utils/mocks/supabase.ts +1 -1
  44. package/src/test-utils/mocks/workos.ts +2 -2
@@ -19426,16 +19426,13 @@ var OntologyKindSchema = z.enum([
19426
19426
  "value-type",
19427
19427
  "property",
19428
19428
  "group",
19429
- "surface"
19429
+ "endpoint"
19430
19430
  ]);
19431
19431
  var SYSTEM_PATH_PATTERN = "[a-z0-9][a-z0-9-]*(?:\\.[a-z0-9][a-z0-9-]*)*";
19432
19432
  var LOCAL_ID_PATTERN = "[a-z0-9][a-z0-9._-]*";
19433
19433
  var ONTOLOGY_ID_PATTERN = `^(global|${SYSTEM_PATH_PATTERN}):(${OntologyKindSchema.options.join("|")})\\/(${LOCAL_ID_PATTERN})$`;
19434
19434
  var ONTOLOGY_ID_REGEX = new RegExp(ONTOLOGY_ID_PATTERN);
19435
- var OntologyIdSchema = z.string().trim().min(1).max(300).regex(
19436
- ONTOLOGY_ID_REGEX,
19437
- "Ontology IDs must use <system-path>:<kind>/<local-id> or global:<kind>/<local-id>"
19438
- );
19435
+ var OntologyIdSchema = z.string().trim().min(1).max(300).regex(ONTOLOGY_ID_REGEX, "Ontology IDs must use <system-path>:<kind>/<local-id> or global:<kind>/<local-id>");
19439
19436
  function parseOntologyId(id) {
19440
19437
  const normalized = OntologyIdSchema.parse(id);
19441
19438
  const match = ONTOLOGY_ID_REGEX.exec(normalized);
@@ -19498,7 +19495,7 @@ var OntologySharedPropertySchema = OntologyRecordBaseSchema.extend({
19498
19495
  var OntologyGroupSchema = OntologyRecordBaseSchema.extend({
19499
19496
  members: OntologyReferenceListSchema
19500
19497
  });
19501
- var OntologySurfaceTypeSchema = OntologyRecordBaseSchema.extend({
19498
+ var OntologyEndpointTypeSchema = OntologyRecordBaseSchema.extend({
19502
19499
  route: z.string().trim().min(1).max(500).optional()
19503
19500
  });
19504
19501
  var OntologyScopeSchema = z.object({
@@ -19511,7 +19508,7 @@ var OntologyScopeSchema = z.object({
19511
19508
  valueTypes: z.record(OntologyIdSchema, OntologyValueTypeSchema).default({}).optional(),
19512
19509
  sharedProperties: z.record(OntologyIdSchema, OntologySharedPropertySchema).default({}).optional(),
19513
19510
  groups: z.record(OntologyIdSchema, OntologyGroupSchema).default({}).optional(),
19514
- surfaces: z.record(OntologyIdSchema, OntologySurfaceTypeSchema).default({}).optional()
19511
+ endpoints: z.record(OntologyIdSchema, OntologyEndpointTypeSchema).default({}).optional()
19515
19512
  }).default({});
19516
19513
  var DEFAULT_ONTOLOGY_SCOPE = {
19517
19514
  valueTypes: {
@@ -19547,7 +19544,7 @@ var SCOPE_KIND = {
19547
19544
  valueTypes: "value-type",
19548
19545
  sharedProperties: "property",
19549
19546
  groups: "group",
19550
- surfaces: "surface"
19547
+ endpoints: "endpoint"
19551
19548
  };
19552
19549
  var SCOPE_KEYS = Object.keys(SCOPE_KIND);
19553
19550
  function listResolvedOntologyRecords(index2) {
@@ -19580,7 +19577,7 @@ function createEmptyIndex() {
19580
19577
  valueTypes: {},
19581
19578
  sharedProperties: {},
19582
19579
  groups: {},
19583
- surfaces: {}
19580
+ endpoints: {}
19584
19581
  };
19585
19582
  }
19586
19583
  function sortResolvedOntologyIndex(index2) {
@@ -20415,8 +20412,6 @@ var SystemEntrySchema = z.object({
20415
20412
  path: PathSchema.optional(),
20416
20413
  /** @deprecated Use ui.icon. Kept for one-cycle Feature compatibility. */
20417
20414
  icon: IconNameSchema.optional(),
20418
- /** @deprecated Feature color token, retained for one-cycle compatibility. */
20419
- color: ColorTokenSchema.optional(),
20420
20415
  /** @deprecated UI placement hint, retained for one-cycle compatibility. */
20421
20416
  uiPosition: UiPositionSchema.optional(),
20422
20417
  /** @deprecated Use lifecycle. */
@@ -20498,7 +20493,7 @@ var ResourceOntologyBindingSchema = z.object({
20498
20493
  /**
20499
20494
  * Optional typed contract binding for this resource's workflow I/O.
20500
20495
  * Each ref is a `package/subpath#ExportName` string that resolves to a
20501
- * Zod schema in `@repo/elevasis-core` (or the consumer's equivalent package).
20496
+ * Zod schema in the tenant-owned organization model package.
20502
20497
  *
20503
20498
  * Absence of this field preserves all existing behavior — it is additive + optional.
20504
20499
  * Tier-1 validation (schema.ts): ref-string shape only (browser-safe, no imports).
@@ -20939,10 +20934,11 @@ var ONTOLOGY_REFERENCE_KEY_KINDS = {
20939
20934
  interfaceType: "interface",
20940
20935
  propertyType: "property",
20941
20936
  groupType: "group",
20942
- surfaceType: "surface",
20937
+ endpointType: "endpoint",
20943
20938
  stepCatalog: "catalog"
20944
20939
  };
20945
- function buildOmCrossRefIndex(model) {
20940
+ var omCompilationContextCache = /* @__PURE__ */ new WeakMap();
20941
+ function buildOmCrossRefIndexFromOntology(model, ontologyCompilation) {
20946
20942
  const systemsById = /* @__PURE__ */ new Map();
20947
20943
  for (const { path, system } of listAllSystems(model)) {
20948
20944
  systemsById.set(path, system);
@@ -20956,7 +20952,6 @@ function buildOmCrossRefIndex(model) {
20956
20952
  const actionIds = new Set(Object.keys(model.actions ?? {}));
20957
20953
  const customerSegmentIds = new Set(Object.keys(model.customers ?? {}));
20958
20954
  const offeringIds = new Set(Object.keys(model.offerings ?? {}));
20959
- const ontologyCompilation = compileOrganizationOntology(model);
20960
20955
  const ontologyIndexByKind = {
20961
20956
  object: ontologyCompilation.ontology.objectTypes,
20962
20957
  link: ontologyCompilation.ontology.linkTypes,
@@ -20967,7 +20962,7 @@ function buildOmCrossRefIndex(model) {
20967
20962
  "value-type": ontologyCompilation.ontology.valueTypes,
20968
20963
  property: ontologyCompilation.ontology.sharedProperties,
20969
20964
  group: ontologyCompilation.ontology.groups,
20970
- surface: ontologyCompilation.ontology.surfaces
20965
+ endpoint: ontologyCompilation.ontology.endpoints
20971
20966
  };
20972
20967
  const ontologyIds = new Set(Object.values(ontologyIndexByKind).flatMap((index2) => Object.keys(index2)));
20973
20968
  const stageIds = /* @__PURE__ */ new Set();
@@ -20995,6 +20990,15 @@ function buildOmCrossRefIndex(model) {
20995
20990
  stageIds
20996
20991
  };
20997
20992
  }
20993
+ function buildOmCompilationContext(model) {
20994
+ const cached = omCompilationContextCache.get(model);
20995
+ if (cached !== void 0) return cached;
20996
+ const ontologyCompilation = compileOrganizationOntology(model);
20997
+ const crossRefIndex = buildOmCrossRefIndexFromOntology(model, ontologyCompilation);
20998
+ const context = { crossRefIndex, ontologyCompilation };
20999
+ omCompilationContextCache.set(model, context);
21000
+ return context;
21001
+ }
20998
21002
  function knowledgeTargetExists(index2, kind, id) {
20999
21003
  if (kind === "system") return index2.systemsById.has(id);
21000
21004
  if (kind === "client") return index2.clientIds.has(id);
@@ -21331,9 +21335,8 @@ function refineOrganizationModel(model, ctx) {
21331
21335
  }
21332
21336
  });
21333
21337
  });
21334
- const idx = buildOmCrossRefIndex(model);
21338
+ const { crossRefIndex: idx, ontologyCompilation } = buildOmCompilationContext(model);
21335
21339
  const { ontologyIndexByKind, ontologyIds } = idx;
21336
- const ontologyCompilation = compileOrganizationOntology(model);
21337
21340
  function topologyTargetExists(ref) {
21338
21341
  if (ref.kind === "system") return systemsById.has(ref.id);
21339
21342
  if (ref.kind === "resource") return resourcesById.has(ref.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevasis/core",
3
- "version": "0.42.1",
3
+ "version": "0.44.0",
4
4
  "license": "MIT",
5
5
  "description": "Minimal shared constants across Elevasis monorepo",
6
6
  "sideEffects": false,
@@ -45,8 +45,8 @@
45
45
  "rollup-plugin-dts": "^6.3.0",
46
46
  "tsup": "^8.0.0",
47
47
  "typescript": "5.9.2",
48
- "@repo/eslint-config": "0.0.0",
49
- "@repo/typescript-config": "0.0.0"
48
+ "@repo/typescript-config": "0.0.0",
49
+ "@repo/eslint-config": "0.0.0"
50
50
  },
51
51
  "dependencies": {
52
52
  "@anthropic-ai/sdk": "^0.62.0",
@@ -51,14 +51,20 @@ export const AccessKeys = {
51
51
  organizationManage: { systemPath: 'permission.org', action: 'manage' },
52
52
  membersManage: { systemPath: 'permission.members', action: 'manage' },
53
53
  rolesManage: { systemPath: 'permission.roles', action: 'manage' },
54
+ /** @reserved Reserved for first-class secret administration routes and command surfaces. */
54
55
  secretsManage: { systemPath: 'permission.secrets', action: 'manage' },
55
56
  operationsRead: { systemPath: 'permission.operations', action: DEFAULT_ACCESS_ACTION },
57
+ /** @reserved Reserved for write-level operations administration beyond read-only monitoring. */
56
58
  operationsManage: { systemPath: 'permission.operations', action: 'manage' },
57
59
  leadGenManage: { systemPath: 'sales.lead-gen', action: 'manage' },
60
+ /** @reserved Reserved for acquisition administration once that permission namespace is wired. */
58
61
  acquisitionManage: { systemPath: 'permission.acquisition', action: 'manage' },
62
+ /** @reserved Reserved for project administration permission catalog compatibility. */
59
63
  projectsManage: { systemPath: 'permission.projects', action: 'manage' },
64
+ /** @reserved Reserved for client administration permission catalog compatibility. */
60
65
  clientsManage: { systemPath: 'permission.clients', action: 'manage' },
61
66
  operationsOverview: { systemPath: 'diagnostic.operations.overview', action: DEFAULT_ACCESS_ACTION },
67
+ /** @reserved Reserved for a diagnostics surface that lists recent operations executions. */
62
68
  operationsRecentExecutions: { systemPath: 'diagnostic.operations.recent-executions', action: DEFAULT_ACCESS_ACTION },
63
69
  monitoringExecutionLogs: { systemPath: 'diagnostic.monitoring.execution-logs', action: DEFAULT_ACCESS_ACTION },
64
70
  monitoringNotifications: { systemPath: 'diagnostic.monitoring.notifications', action: DEFAULT_ACCESS_ACTION }
@@ -10,7 +10,7 @@ export const LeadGenStageKeySchema = z.string().trim().min(1)
10
10
 
11
11
  export const LeadGenActionKeySchema = z.string().trim().min(1)
12
12
 
13
- // CRM stage/state catalogs are model-owned (authored in @repo/elevasis-core
13
+ // CRM stage/state catalogs are model-owned (authored in the tenant-owned organization model package
14
14
  // canonicalOrganizationModel). The published core schema validates only the
15
15
  // transport shape; closed-catalog membership is enforced by the caller/API
16
16
  // layer via model-injected validators (mirrors LeadGenStageKeySchema).
@@ -12,7 +12,7 @@ import { z } from 'zod'
12
12
  * layer; use the row types internally for direct Supabase access.
13
13
  *
14
14
  * Usage in a client project:
15
- * import { BaseProject } from '@elevasis/core' (or '@repo/core' internally)
15
+ * import { BaseProject } from '@elevasis/core'
16
16
  * type Project = BaseProject<{ budget: number; riskScore: 'low' | 'medium' | 'high' }>
17
17
  *
18
18
  * Extending a Zod schema in a client project:
@@ -0,0 +1,211 @@
1
+ import type {
2
+ OrganizationModel,
3
+ OrganizationModelAction,
4
+ OrganizationModelActionInvocation,
5
+ OrganizationModelAgentResourceEntry
6
+ } from '../organization-model/types'
7
+ import { getSystem } from '../organization-model/helpers'
8
+ import type { listAllResources, listAllRoles, listAllSystemsFlat, OmSearchHitKind } from './queries'
9
+
10
+ export const OM_SEARCH_HIT_KINDS: OmSearchHitKind[] = ['system', 'resource', 'knowledge', 'ontology', 'role', 'policy']
11
+
12
+ export function parseKnowledgeSearchLimit(raw: string | undefined, commandName = 'knowledge:search'): number {
13
+ if (raw === undefined) return 10
14
+ const n = Number.parseInt(raw, 10)
15
+ if (Number.isNaN(n) || n < 0) {
16
+ throw new Error(`${commandName}: --limit must be a non-negative integer (got "${raw}")`)
17
+ }
18
+ return n
19
+ }
20
+
21
+ export function parseKnowledgeSearchKinds(
22
+ raw: string | undefined,
23
+ commandName = 'knowledge:search'
24
+ ): OmSearchHitKind[] | undefined {
25
+ if (raw === undefined) return undefined
26
+ const requested = raw
27
+ .split(',')
28
+ .map((s) => s.trim().toLowerCase())
29
+ .filter((s) => s.length > 0)
30
+ const valid: OmSearchHitKind[] = []
31
+ const invalid: string[] = []
32
+ for (const kind of requested) {
33
+ if (OM_SEARCH_HIT_KINDS.includes(kind as OmSearchHitKind)) {
34
+ valid.push(kind as OmSearchHitKind)
35
+ } else {
36
+ invalid.push(kind)
37
+ }
38
+ }
39
+ if (invalid.length > 0) {
40
+ throw new Error(`${commandName}: unknown kind(s) "${invalid.join(', ')}". Valid: ${OM_SEARCH_HIT_KINDS.join(', ')}`)
41
+ }
42
+ return valid.length > 0 ? valid : undefined
43
+ }
44
+
45
+ export function formatKnowledgeSystemsList(entries: ReturnType<typeof listAllSystemsFlat>): string {
46
+ if (entries.length === 0) return '(no results)'
47
+ const pathWidth = Math.max(...entries.map((e) => e.path.length), 4)
48
+ const header = `${'PATH'.padEnd(pathWidth)} LABEL`
49
+ const divider = '-'.repeat(header.length + 20)
50
+ const rows = entries.map((e) => {
51
+ const label = e.system.label ?? e.system.title ?? e.path
52
+ return `${e.path.padEnd(pathWidth)} ${label}`
53
+ })
54
+ return [header, divider, ...rows].join('\n')
55
+ }
56
+
57
+ export function formatKnowledgeResourcesList(resources: ReturnType<typeof listAllResources>): string {
58
+ if (resources.length === 0) return '(no results)'
59
+ const idWidth = Math.max(...resources.map((r) => r.id.length), 4)
60
+ const kindWidth = Math.max(...resources.map((r) => r.kind.length), 4)
61
+ const header = `${'ID'.padEnd(idWidth)} ${'KIND'.padEnd(kindWidth)} TITLE`
62
+ const divider = '-'.repeat(header.length + 20)
63
+ const rows = resources.map((r) => {
64
+ const title = r.title ?? r.id
65
+ return `${r.id.padEnd(idWidth)} ${r.kind.padEnd(kindWidth)} ${title}`
66
+ })
67
+ return [header, divider, ...rows].join('\n')
68
+ }
69
+
70
+ export function formatKnowledgeRolesList(roles: ReturnType<typeof listAllRoles>): string {
71
+ if (roles.length === 0) return '(no results)'
72
+ const idWidth = Math.max(...roles.map((r) => r.id.length), 4)
73
+ const header = `${'ID'.padEnd(idWidth)} TITLE`
74
+ const divider = '-'.repeat(header.length + 20)
75
+ const rows = roles.map((r) => `${r.id.padEnd(idWidth)} ${r.title}`)
76
+ return [header, divider, ...rows].join('\n')
77
+ }
78
+
79
+ export interface KnowledgeInvocationSource {
80
+ kind: 'action' | 'agent-resource'
81
+ id: string
82
+ label: string
83
+ targetNodeId: string
84
+ invocations: OrganizationModelActionInvocation[]
85
+ }
86
+
87
+ export interface KnowledgeInvocationNeighborsResult {
88
+ id: string
89
+ title: string
90
+ invocationSources: KnowledgeInvocationSource[]
91
+ }
92
+
93
+ export function normalizeKnowledgeNodeId(id: string): string {
94
+ return id.startsWith('knowledge:') ? id.slice('knowledge:'.length) : id
95
+ }
96
+
97
+ export function formatKnowledgeInvocationLabel(invocation: OrganizationModelActionInvocation): string {
98
+ switch (invocation.kind) {
99
+ case 'slash-command':
100
+ return invocation.command
101
+ case 'mcp-tool':
102
+ return `${invocation.server}/${invocation.name}`
103
+ case 'api-endpoint':
104
+ return `${invocation.method} ${invocation.path}`
105
+ case 'script-execution':
106
+ return invocation.resourceId
107
+ }
108
+ }
109
+
110
+ function pushActionSource(
111
+ sources: KnowledgeInvocationSource[],
112
+ seen: Set<string>,
113
+ action: OrganizationModelAction | undefined
114
+ ): void {
115
+ if (!action || action.invocations.length === 0) return
116
+
117
+ const key = `action:${action.id}`
118
+ if (seen.has(key)) return
119
+ seen.add(key)
120
+
121
+ sources.push({
122
+ kind: 'action',
123
+ id: action.id,
124
+ label: action.label,
125
+ targetNodeId: `action:${action.id}`,
126
+ invocations: action.invocations
127
+ })
128
+ }
129
+
130
+ function pushAgentResourceSource(
131
+ sources: KnowledgeInvocationSource[],
132
+ seen: Set<string>,
133
+ resource: OrganizationModel['resources'][string] | undefined
134
+ ): void {
135
+ if (!resource || resource.kind !== 'agent' || resource.invocations.length === 0) return
136
+
137
+ const agentResource = resource as OrganizationModelAgentResourceEntry
138
+ const key = `agent-resource:${agentResource.id}`
139
+ if (seen.has(key)) return
140
+ seen.add(key)
141
+
142
+ sources.push({
143
+ kind: 'agent-resource',
144
+ id: agentResource.id,
145
+ label: agentResource.id,
146
+ targetNodeId: `resource:${agentResource.id}`,
147
+ invocations: agentResource.invocations
148
+ })
149
+ }
150
+
151
+ export function resolveKnowledgeInvocationNeighbors(
152
+ model: OrganizationModel,
153
+ id: string
154
+ ): KnowledgeInvocationNeighborsResult | undefined {
155
+ const normalizedNodeId = normalizeKnowledgeNodeId(id)
156
+ const node = model.knowledge[normalizedNodeId]
157
+ if (!node) return undefined
158
+
159
+ const sources: KnowledgeInvocationSource[] = []
160
+ const seen = new Set<string>()
161
+
162
+ for (const link of node.links) {
163
+ const target = link.target
164
+
165
+ if (target.kind === 'action') {
166
+ pushActionSource(sources, seen, model.actions[target.id])
167
+ continue
168
+ }
169
+
170
+ if (target.kind === 'resource') {
171
+ pushAgentResourceSource(sources, seen, model.resources[target.id])
172
+ continue
173
+ }
174
+
175
+ if (target.kind === 'system') {
176
+ const system = getSystem(model, target.id)
177
+ for (const actionRef of system?.actions ?? []) {
178
+ pushActionSource(sources, seen, model.actions[actionRef.actionId])
179
+ }
180
+
181
+ for (const resource of Object.values(model.resources)) {
182
+ if (resource.systemPath === target.id) {
183
+ pushAgentResourceSource(sources, seen, resource)
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ return {
190
+ id: node.id,
191
+ title: node.title,
192
+ invocationSources: sources.sort((a, b) => a.targetNodeId.localeCompare(b.targetNodeId))
193
+ }
194
+ }
195
+
196
+ export function formatKnowledgeInvocationNeighbors(result: KnowledgeInvocationNeighborsResult): string {
197
+ const lines = [`Invocation-bearing graph neighbors for ${result.id}`, '']
198
+
199
+ if (result.invocationSources.length === 0) {
200
+ lines.push(' (none)')
201
+ } else {
202
+ for (const source of result.invocationSources) {
203
+ lines.push(`${source.kind}: ${source.id}`)
204
+ for (const invocation of source.invocations) {
205
+ lines.push(` ${invocation.kind}: ${formatKnowledgeInvocationLabel(invocation)}`)
206
+ }
207
+ }
208
+ }
209
+
210
+ return lines.join('\n')
211
+ }
@@ -29,3 +29,16 @@ export type {
29
29
 
30
30
  export { formatText, formatJson, formatIdsOnly, formatOmSearchHits, formatOmDescribe } from './format'
31
31
  export type { KnowledgeJsonEnvelope } from './format'
32
+ export {
33
+ OM_SEARCH_HIT_KINDS,
34
+ parseKnowledgeSearchLimit,
35
+ parseKnowledgeSearchKinds,
36
+ formatKnowledgeSystemsList,
37
+ formatKnowledgeResourcesList,
38
+ formatKnowledgeRolesList,
39
+ normalizeKnowledgeNodeId,
40
+ formatKnowledgeInvocationLabel,
41
+ resolveKnowledgeInvocationNeighbors,
42
+ formatKnowledgeInvocationNeighbors
43
+ } from './cli-helpers'
44
+ export type { KnowledgeInvocationSource, KnowledgeInvocationNeighborsResult } from './cli-helpers'
@@ -1,5 +1,18 @@
1
- export { bySystem, byKind, byOwner, governs, governedBy, parsePath } from './queries'
2
- export type { KnowledgeMount, ParsedKnowledgePath } from './queries'
3
-
4
- export { formatText, formatJson, formatIdsOnly } from './format'
5
- export type { KnowledgeJsonEnvelope } from './format'
1
+ export { bySystem, byKind, byOwner, governs, governedBy, parsePath } from './queries'
2
+ export type { KnowledgeMount, ParsedKnowledgePath, OmSearchHitKind } from './queries'
3
+
4
+ export { formatText, formatJson, formatIdsOnly } from './format'
5
+ export type { KnowledgeJsonEnvelope } from './format'
6
+ export {
7
+ OM_SEARCH_HIT_KINDS,
8
+ parseKnowledgeSearchLimit,
9
+ parseKnowledgeSearchKinds,
10
+ formatKnowledgeSystemsList,
11
+ formatKnowledgeResourcesList,
12
+ formatKnowledgeRolesList,
13
+ normalizeKnowledgeNodeId,
14
+ formatKnowledgeInvocationLabel,
15
+ resolveKnowledgeInvocationNeighbors,
16
+ formatKnowledgeInvocationNeighbors
17
+ } from './cli-helpers'
18
+ export type { KnowledgeInvocationSource, KnowledgeInvocationNeighborsResult } from './cli-helpers'
@@ -719,7 +719,7 @@ function collectOntologyEntries(scope: {
719
719
  valueTypes?: Record<string, OntologyRecordLike>
720
720
  sharedProperties?: Record<string, OntologyRecordLike>
721
721
  groups?: Record<string, OntologyRecordLike>
722
- surfaces?: Record<string, OntologyRecordLike>
722
+ endpoints?: Record<string, OntologyRecordLike>
723
723
  }): SearchableEntry[] {
724
724
  const entries: SearchableEntry[] = []
725
725
 
@@ -733,7 +733,7 @@ function collectOntologyEntries(scope: {
733
733
  ['value-type', scope.valueTypes],
734
734
  ['property', scope.sharedProperties],
735
735
  ['group', scope.groups],
736
- ['surface', scope.surfaces]
736
+ ['endpoint', scope.endpoints]
737
737
  ]
738
738
 
739
739
  for (const [subKind, records] of buckets) {
@@ -874,7 +874,7 @@ export interface OmDescribePolicy {
874
874
  * - Matches a key in `model.resources` → resource
875
875
  */
876
876
  function detectKind(model: OrganizationModel, id: string): OmSearchHitKind | undefined {
877
- if (/:(object|action|event|catalog|link|interface|value-type|property|group|surface)\//.test(id)) {
877
+ if (/:(object|action|event|catalog|link|interface|value-type|property|group|endpoint)\//.test(id)) {
878
878
  return 'ontology'
879
879
  }
880
880
  if (id.startsWith('knowledge.')) return 'knowledge'
@@ -972,7 +972,7 @@ function describeSystem(model: OrganizationModel, path: string): OmDescribeSyste
972
972
  ['value-type', ontology.valueTypes],
973
973
  ['property', ontology.sharedProperties],
974
974
  ['group', ontology.groups],
975
- ['surface', ontology.surfaces]
975
+ ['endpoint', ontology.endpoints]
976
976
  ]
977
977
  for (const [k, records] of buckets) {
978
978
  const count = records ? Object.keys(records).length : 0
@@ -1058,7 +1058,7 @@ function describeOntology(model: OrganizationModel, id: string): OmDescribeOntol
1058
1058
  ['value-type', 'valueTypes'],
1059
1059
  ['property', 'sharedProperties'],
1060
1060
  ['group', 'groups'],
1061
- ['surface', 'surfaces']
1061
+ ['endpoint', 'endpoints']
1062
1062
  ]
1063
1063
  const bucketKey = buckets.find((b) => b[0] === ontologyKind)?.[1]
1064
1064
  if (!bucketKey) return undefined
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import { DEFAULT_ORGANIZATION_MODEL } from '../defaults'
3
- import { buildOmCrossRefIndex, knowledgeTargetExists } from '../cross-ref'
3
+ import { buildOmCompilationContext, buildOmCrossRefIndex, knowledgeTargetExists } from '../cross-ref'
4
4
  import { OrganizationModelSchema } from '../schema'
5
5
  import type { OrganizationModel } from '../types'
6
6
 
@@ -80,6 +80,16 @@ function makeModelWithStage(): OrganizationModel {
80
80
  // ---------------------------------------------------------------------------
81
81
 
82
82
  describe('knowledgeTargetExists — stage kind', () => {
83
+ it('buildOmCompilationContext returns a memoized cross-ref index plus ontology compilation', () => {
84
+ const model = makeModelWithStage()
85
+ const context = buildOmCompilationContext(model)
86
+
87
+ expect(buildOmCompilationContext(model)).toBe(context)
88
+ expect(context.crossRefIndex.stageIds.has('prospect')).toBe(true)
89
+ expect(context.ontologyCompilation.ontology.catalogTypes['sales.lead-gen:catalog/company-stage']).toBeDefined()
90
+ expect(buildOmCrossRefIndex(model)).toBe(context.crossRefIndex)
91
+ })
92
+
83
93
  it('returns true for a stage id that exists in a stage-kind catalog', () => {
84
94
  const model = makeModelWithStage()
85
95
  const idx = buildOmCrossRefIndex(model)
@@ -123,10 +123,10 @@ describe('SystemEntrySchema - positive parse', () => {
123
123
  }
124
124
  })
125
125
 
126
- it('accepts deprecated status and maps it to lifecycle for one-cycle compatibility', () => {
127
- const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
128
-
129
- const result = SystemEntrySchema.safeParse({
126
+ it('accepts deprecated status and maps it to lifecycle for one-cycle compatibility', () => {
127
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
128
+
129
+ const result = SystemEntrySchema.safeParse({
130
130
  ...VALID_SYSTEM,
131
131
  status: 'deprecated'
132
132
  })
@@ -137,10 +137,32 @@ describe('SystemEntrySchema - positive parse', () => {
137
137
  expect(result.data.lifecycle).toBe('deprecated')
138
138
  }
139
139
  expect(warn).toHaveBeenCalledWith('[organization-model] System.status is deprecated; use System.lifecycle instead.')
140
-
141
- warn.mockRestore()
142
- })
143
- })
140
+
141
+ warn.mockRestore()
142
+ })
143
+
144
+ it('keeps non-color compatibility fields readable', () => {
145
+ const result = SystemEntrySchema.safeParse({
146
+ ...VALID_SYSTEM,
147
+ path: '/lead-gen',
148
+ icon: 'settings',
149
+ uiPosition: 'sidebar-primary',
150
+ enabled: true,
151
+ devOnly: false
152
+ })
153
+
154
+ expect(result.success).toBe(true)
155
+ if (result.success) {
156
+ expect(result.data).toMatchObject({
157
+ path: '/lead-gen',
158
+ icon: 'settings',
159
+ uiPosition: 'sidebar-primary',
160
+ enabled: true,
161
+ devOnly: false
162
+ })
163
+ }
164
+ })
165
+ })
144
166
 
145
167
  describe('SystemEntrySchema - negative parse', () => {
146
168
  it('rejects missing required fields', () => {
@@ -159,6 +181,10 @@ describe('SystemEntrySchema - negative parse', () => {
159
181
  expect(SystemEntrySchema.safeParse({ ...VALID_SYSTEM, id: 'Sys Lead Gen' }).success).toBe(false)
160
182
  })
161
183
 
184
+ it('rejects the retired color compatibility field', () => {
185
+ expect(SystemEntrySchema.safeParse({ ...VALID_SYSTEM, color: 'blue' }).success).toBe(false)
186
+ })
187
+
162
188
  it('rejects malformed System Interface states', () => {
163
189
  expect(SystemApiInterfaceSchema.safeParse({ kind: 'api', lifecycle: 'active' }).success).toBe(false)
164
190
  expect(SystemApiInterfaceSchema.safeParse({ lifecycle: 'paused' }).success).toBe(false)
@@ -44,7 +44,11 @@ describe('organization-model scaffolders', () => {
44
44
  'packages/elevasis-core/src/organization-model/knowledge.ts'
45
45
  ])
46
46
  )
47
- expect(plan.writes.some((write) => write.path.endsWith('manifest.stub.ts'))).toBe(true)
47
+ const manifestStub = plan.writes.find((write) => write.path.endsWith('manifest.stub.ts'))
48
+ expect(manifestStub?.content).toContain('key: "sales-partners"')
49
+ expect(manifestStub?.content).toContain('systemId: "sales.partners"')
50
+ expect(manifestStub?.content).not.toContain('label:')
51
+ expect(manifestStub?.content).not.toContain('routes:')
48
52
  expect(plan.writes.some((write) => write.path.includes('/routes/'))).toBe(false)
49
53
  expect(plan.projectCommand).toBeUndefined()
50
54
  })
@@ -82,6 +86,31 @@ describe('organization-model scaffolders', () => {
82
86
  expect(knowledge.projectCommand).toBeUndefined()
83
87
  })
84
88
 
89
+ it('emits required link ontology stubs', () => {
90
+ const plan = scaffoldOrganizationModel(BASE_MODEL, {
91
+ intent: 'ontology',
92
+ id: 'deal-owner',
93
+ systemPath: 'sales.crm',
94
+ kind: 'link'
95
+ })
96
+
97
+ expect(plan.edits[0]?.snippet).toContain('"sales.crm:link/deal-owner": {')
98
+ expect(plan.edits[0]?.snippet).toContain('"from": "sales.crm:object/source-object"')
99
+ expect(plan.edits[0]?.snippet).toContain('"to": "sales.crm:object/target-object"')
100
+ })
101
+
102
+ it('emits action ontology actsOn stubs', () => {
103
+ const plan = scaffoldOrganizationModel(BASE_MODEL, {
104
+ intent: 'ontology',
105
+ id: 'update-deal',
106
+ systemPath: 'sales.crm',
107
+ kind: 'action'
108
+ })
109
+
110
+ expect(plan.edits[0]?.snippet).toContain('"sales.crm:action/update-deal": {')
111
+ expect(plan.edits[0]?.snippet).toContain('"actsOn": []')
112
+ })
113
+
85
114
  it('rejects duplicate system paths during planning', () => {
86
115
  expect(() =>
87
116
  scaffoldOrganizationModel(BASE_MODEL, {