@elevasis/core 0.21.0 → 0.23.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 (132) hide show
  1. package/dist/index.d.ts +2518 -2169
  2. package/dist/index.js +2495 -1095
  3. package/dist/knowledge/index.d.ts +706 -1044
  4. package/dist/knowledge/index.js +9 -9
  5. package/dist/organization-model/index.d.ts +2518 -2169
  6. package/dist/organization-model/index.js +2495 -1095
  7. package/dist/test-utils/index.d.ts +826 -1014
  8. package/dist/test-utils/index.js +1894 -1032
  9. package/package.json +3 -3
  10. package/src/__tests__/template-core-compatibility.test.ts +11 -79
  11. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +852 -397
  12. package/src/auth/multi-tenancy/permissions.ts +20 -8
  13. package/src/business/README.md +2 -2
  14. package/src/business/acquisition/api-schemas.test.ts +175 -2
  15. package/src/business/acquisition/api-schemas.ts +132 -16
  16. package/src/business/acquisition/build-templates.test.ts +4 -4
  17. package/src/business/acquisition/build-templates.ts +72 -30
  18. package/src/business/acquisition/crm-state-actions.test.ts +13 -11
  19. package/src/business/acquisition/index.ts +12 -0
  20. package/src/business/acquisition/types.ts +7 -3
  21. package/src/business/clients/api-schemas.test.ts +115 -0
  22. package/src/business/clients/api-schemas.ts +158 -0
  23. package/src/business/clients/index.ts +1 -0
  24. package/src/business/deals/api-schemas.ts +8 -0
  25. package/src/business/index.ts +5 -2
  26. package/src/business/projects/types.ts +19 -0
  27. package/src/execution/engine/__tests__/fixtures/test-agents.ts +10 -8
  28. package/src/execution/engine/agent/core/__tests__/agent.test.ts +16 -12
  29. package/src/execution/engine/agent/core/__tests__/error-passthrough.test.ts +4 -3
  30. package/src/execution/engine/agent/core/types.ts +25 -15
  31. package/src/execution/engine/agent/index.ts +6 -4
  32. package/src/execution/engine/agent/reasoning/__tests__/request-builder.test.ts +24 -18
  33. package/src/execution/engine/index.ts +3 -0
  34. package/src/execution/engine/workflow/types.ts +9 -2
  35. package/src/knowledge/README.md +8 -7
  36. package/src/knowledge/__tests__/queries.test.ts +74 -73
  37. package/src/knowledge/format.ts +10 -9
  38. package/src/knowledge/index.ts +1 -1
  39. package/src/knowledge/published.ts +1 -1
  40. package/src/knowledge/queries.ts +26 -25
  41. package/src/organization-model/README.md +73 -26
  42. package/src/organization-model/__tests__/content-kinds-registry.test.ts +210 -0
  43. package/src/organization-model/__tests__/defaults.test.ts +76 -96
  44. package/src/organization-model/__tests__/domains/actions.test.ts +56 -0
  45. package/src/organization-model/__tests__/domains/customers.test.ts +299 -295
  46. package/src/organization-model/__tests__/domains/entities.test.ts +56 -0
  47. package/src/organization-model/__tests__/domains/goals.test.ts +493 -479
  48. package/src/organization-model/__tests__/domains/identity.test.ts +280 -279
  49. package/src/organization-model/__tests__/domains/navigation.test.ts +268 -212
  50. package/src/organization-model/__tests__/domains/offerings.test.ts +414 -419
  51. package/src/organization-model/__tests__/domains/policies.test.ts +323 -0
  52. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +271 -271
  53. package/src/organization-model/__tests__/domains/resources.test.ts +310 -0
  54. package/src/organization-model/__tests__/domains/roles.test.ts +463 -347
  55. package/src/organization-model/__tests__/domains/statuses.test.ts +246 -243
  56. package/src/organization-model/__tests__/domains/systems.test.ts +209 -0
  57. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +361 -0
  58. package/src/organization-model/__tests__/foundation.test.ts +74 -102
  59. package/src/organization-model/__tests__/get-resources-for-system.test.ts +144 -0
  60. package/src/organization-model/__tests__/graph.test.ts +899 -71
  61. package/src/organization-model/__tests__/knowledge.test.ts +209 -49
  62. package/src/organization-model/__tests__/lookup-helpers.test.ts +438 -0
  63. package/src/organization-model/__tests__/migration-helpers.test.ts +591 -0
  64. package/src/organization-model/__tests__/prospecting-ssot.test.ts +36 -27
  65. package/src/organization-model/__tests__/recursive-system-schema.test.ts +520 -0
  66. package/src/organization-model/__tests__/resolve.test.ts +174 -23
  67. package/src/organization-model/__tests__/schema.test.ts +291 -114
  68. package/src/organization-model/__tests__/surface-projection.test.ts +207 -97
  69. package/src/organization-model/catalogs/lead-gen.ts +144 -0
  70. package/src/organization-model/content-kinds/config.ts +36 -0
  71. package/src/organization-model/content-kinds/index.ts +74 -0
  72. package/src/organization-model/content-kinds/pipeline.ts +68 -0
  73. package/src/organization-model/content-kinds/registry.ts +44 -0
  74. package/src/organization-model/content-kinds/status.ts +71 -0
  75. package/src/organization-model/content-kinds/template.ts +83 -0
  76. package/src/organization-model/content-kinds/types.ts +117 -0
  77. package/src/organization-model/contracts.ts +13 -3
  78. package/src/organization-model/defaults.ts +499 -86
  79. package/src/organization-model/domains/actions.ts +239 -0
  80. package/src/organization-model/domains/customers.ts +78 -75
  81. package/src/organization-model/domains/entities.ts +144 -0
  82. package/src/organization-model/domains/goals.ts +83 -80
  83. package/src/organization-model/domains/knowledge.ts +76 -17
  84. package/src/organization-model/domains/navigation.ts +107 -384
  85. package/src/organization-model/domains/offerings.ts +71 -66
  86. package/src/organization-model/domains/policies.ts +102 -0
  87. package/src/organization-model/domains/projects.ts +14 -48
  88. package/src/organization-model/domains/prospecting.ts +62 -181
  89. package/src/organization-model/domains/resources.ts +145 -0
  90. package/src/organization-model/domains/roles.ts +96 -55
  91. package/src/organization-model/domains/sales.ts +10 -219
  92. package/src/organization-model/domains/shared.ts +57 -57
  93. package/src/organization-model/domains/statuses.ts +339 -130
  94. package/src/organization-model/domains/systems.ts +203 -0
  95. package/src/organization-model/foundation.ts +54 -67
  96. package/src/organization-model/graph/build.ts +682 -54
  97. package/src/organization-model/graph/link.ts +1 -1
  98. package/src/organization-model/graph/schema.ts +24 -9
  99. package/src/organization-model/graph/types.ts +20 -7
  100. package/src/organization-model/helpers.ts +231 -26
  101. package/src/organization-model/icons.ts +1 -0
  102. package/src/organization-model/index.ts +118 -5
  103. package/src/organization-model/migration-helpers.ts +249 -0
  104. package/src/organization-model/organization-graph.mdx +16 -15
  105. package/src/organization-model/organization-model.mdx +111 -44
  106. package/src/organization-model/published.ts +172 -19
  107. package/src/organization-model/resolve.ts +117 -54
  108. package/src/organization-model/schema.ts +654 -112
  109. package/src/organization-model/surface-projection.ts +116 -122
  110. package/src/organization-model/types.ts +146 -20
  111. package/src/platform/api/types.ts +38 -35
  112. package/src/platform/constants/versions.ts +1 -1
  113. package/src/platform/registry/__tests__/command-view.test.ts +6 -8
  114. package/src/platform/registry/__tests__/resource-link.test.ts +13 -8
  115. package/src/platform/registry/__tests__/resource-registry.integration.test.ts +16 -31
  116. package/src/platform/registry/__tests__/resource-registry.nested-systems.test.ts +245 -0
  117. package/src/platform/registry/__tests__/resource-registry.test.ts +2053 -2005
  118. package/src/platform/registry/__tests__/validation.test.ts +1347 -1086
  119. package/src/platform/registry/index.ts +14 -0
  120. package/src/platform/registry/resource-registry.ts +52 -2
  121. package/src/platform/registry/serialization.ts +241 -202
  122. package/src/platform/registry/serialized-types.ts +1 -0
  123. package/src/platform/registry/types.ts +411 -361
  124. package/src/platform/registry/validation.ts +745 -513
  125. package/src/projects/api-schemas.ts +290 -267
  126. package/src/reference/_generated/contracts.md +853 -397
  127. package/src/reference/glossary.md +23 -18
  128. package/src/supabase/database.types.ts +181 -0
  129. package/src/test-utils/test-utils.test.ts +1 -6
  130. package/src/organization-model/__tests__/domains/operations.test.ts +0 -203
  131. package/src/organization-model/domains/features.ts +0 -31
  132. package/src/organization-model/domains/operations.ts +0 -85
@@ -35,9 +35,10 @@ vi.mock('../../memory/processor', () => ({
35
35
  const mockAdapterFactory: LLMAdapterFactory = vi.fn() as unknown as LLMAdapterFactory
36
36
 
37
37
  const createTestAgent = (): Agent => {
38
- const config: AgentConfig = {
39
- type: 'agent',
40
- resourceId: 'test-agent',
38
+ const config: AgentConfig = {
39
+ type: 'agent',
40
+ kind: 'utility',
41
+ resourceId: 'test-agent',
41
42
  name: 'Test Agent',
42
43
  description: 'Agent for error passthrough testing',
43
44
  environment: 'dev',
@@ -3,9 +3,10 @@
3
3
  * Types for autonomous agents with tools, memory, and constraints
4
4
  */
5
5
 
6
- import type { Contract, ExecutionContext } from '../../base/types'
7
- import type { ResourceDefinition } from '../../../../platform/registry/types'
8
- import { Tool } from '../../tools/types'
6
+ import type { Contract, ExecutionContext } from '../../base/types'
7
+ import type { ResourceDefinition } from '../../../../platform/registry/types'
8
+ import type { AgentResourceEntry } from '../../../../organization-model/domains/resources'
9
+ import { Tool } from '../../tools/types'
9
10
  import type { AgentMemory } from '../memory/types'
10
11
  import type { LLMAdapter } from '../../llm/types'
11
12
  import type { ModelConfig } from '../../llm/model-info'
@@ -27,17 +28,22 @@ export type { AgentMemory }
27
28
  * Uses `any` for optional params so both the real createLLMAdapter (with typed
28
29
  * AIUsageCollector/AICallContext) and the worker proxy (which ignores them) satisfy the type.
29
30
  */
30
- export type LLMAdapterFactory = (
31
- config: ModelConfig,
32
- ...args: any[]
33
- ) => LLMAdapter
34
-
35
- // Agent configuration
36
- export interface AgentConfig extends ResourceDefinition {
37
- type: 'agent'
38
-
39
- // Agent behavior
40
- systemPrompt: string // System prompt defining agent behavior
31
+ export type LLMAdapterFactory = (
32
+ config: ModelConfig,
33
+ ...args: any[]
34
+ ) => LLMAdapter
35
+
36
+ export type AgentKind = 'orchestrator' | 'specialist' | 'utility' | 'platform'
37
+
38
+ // Agent configuration
39
+ export interface AgentConfig extends ResourceDefinition {
40
+ type: 'agent'
41
+ /** OM descriptor backing canonical identity and governance metadata. */
42
+ resource?: AgentResourceEntry
43
+ kind: AgentKind
44
+
45
+ // Agent behavior
46
+ systemPrompt: string // System prompt defining agent behavior
41
47
 
42
48
  // Execution constraints (simplified for v1)
43
49
  constraints?: AgentConstraints
@@ -83,7 +89,11 @@ export interface AgentConfig extends ResourceDefinition {
83
89
  memoryPreferences?: string
84
90
 
85
91
  // Lifecycle callbacks for observability (optional) - DEFERRED
86
- }
92
+ }
93
+
94
+ export type DescriptorBackedAgentConfig = Omit<AgentConfig, 'resourceId' | 'type' | 'resource'> & {
95
+ resource: AgentResourceEntry
96
+ }
87
97
 
88
98
  // Execution constraints to prevent runaway agents
89
99
  export interface AgentConstraints {
@@ -7,10 +7,12 @@
7
7
  export { Agent } from './core/agent'
8
8
 
9
9
  // Types
10
- export type {
11
- AgentConfig,
12
- AgentConstraints,
13
- AgentDefinition,
10
+ export type {
11
+ AgentKind,
12
+ AgentConfig,
13
+ DescriptorBackedAgentConfig,
14
+ AgentConstraints,
15
+ AgentDefinition,
14
16
  IterationContext,
15
17
  LLMAdapterFactory
16
18
  } from './core/types'
@@ -8,9 +8,10 @@ import { z } from 'zod'
8
8
  describe('buildReasoningRequest', () => {
9
9
  // Helper: Create minimal iteration context
10
10
  const createMockContext = (overrides?: Partial<IterationContext>): IterationContext => {
11
- const config: AgentConfig = {
12
- type: 'agent',
13
- resourceId: 'test-agent',
11
+ const config: AgentConfig = {
12
+ type: 'agent',
13
+ kind: 'utility',
14
+ resourceId: 'test-agent',
14
15
  name: 'Test Agent',
15
16
  description: 'Test agent',
16
17
  version: '1.0.0',
@@ -95,9 +96,10 @@ describe('buildReasoningRequest', () => {
95
96
 
96
97
  it('should set includeMessageAction to true when sessionCapable is true', () => {
97
98
  const context = createMockContext({
98
- config: {
99
- type: 'agent',
100
- resourceId: 'test-agent',
99
+ config: {
100
+ type: 'agent',
101
+ kind: 'utility',
102
+ resourceId: 'test-agent',
101
103
  name: 'Test Agent',
102
104
  description: 'Test agent',
103
105
  version: '1.0.0',
@@ -113,9 +115,10 @@ describe('buildReasoningRequest', () => {
113
115
 
114
116
  it('should set includeMessageAction to false when sessionCapable is false', () => {
115
117
  const context = createMockContext({
116
- config: {
117
- type: 'agent',
118
- resourceId: 'test-agent',
118
+ config: {
119
+ type: 'agent',
120
+ kind: 'utility',
121
+ resourceId: 'test-agent',
119
122
  name: 'Test Agent',
120
123
  description: 'Test agent',
121
124
  version: '1.0.0',
@@ -142,9 +145,10 @@ describe('buildReasoningRequest', () => {
142
145
 
143
146
  it('should include hardened security rules when sessionCapable is true', () => {
144
147
  const context = createMockContext({
145
- config: {
146
- type: 'agent',
147
- resourceId: 'test-agent',
148
+ config: {
149
+ type: 'agent',
150
+ kind: 'utility',
151
+ resourceId: 'test-agent',
148
152
  name: 'Test Agent',
149
153
  description: 'Test agent',
150
154
  version: '1.0.0',
@@ -161,9 +165,10 @@ describe('buildReasoningRequest', () => {
161
165
 
162
166
  it('should omit security section when securityLevel is none', () => {
163
167
  const context = createMockContext({
164
- config: {
165
- type: 'agent',
166
- resourceId: 'test-agent',
168
+ config: {
169
+ type: 'agent',
170
+ kind: 'utility',
171
+ resourceId: 'test-agent',
167
172
  name: 'Test Agent',
168
173
  description: 'Test agent',
169
174
  version: '1.0.0',
@@ -179,9 +184,10 @@ describe('buildReasoningRequest', () => {
179
184
 
180
185
  it('should allow explicit hardened override on non-session agent', () => {
181
186
  const context = createMockContext({
182
- config: {
183
- type: 'agent',
184
- resourceId: 'test-agent',
187
+ config: {
188
+ type: 'agent',
189
+ kind: 'utility',
190
+ resourceId: 'test-agent',
185
191
  name: 'Test Agent',
186
192
  description: 'Test agent',
187
193
  version: '1.0.0',
@@ -14,6 +14,7 @@ export {
14
14
  type WorkflowStepDefinition,
15
15
  type StepHandler,
16
16
  type WorkflowConfig,
17
+ type DescriptorBackedWorkflowConfig,
17
18
  type WorkflowDefinition,
18
19
  type NextConfig,
19
20
  type LinearNext,
@@ -59,7 +60,9 @@ export {
59
60
  // Agent exports (from agent module)
60
61
  export {
61
62
  Agent,
63
+ type AgentKind,
62
64
  type AgentConfig,
65
+ type DescriptorBackedAgentConfig,
63
66
  type AgentConstraints,
64
67
  type AgentMemory,
65
68
  type AgentDefinition,
@@ -7,14 +7,21 @@ import type { z } from 'zod'
7
7
 
8
8
  import type { ExecutionContext } from '../base/types'
9
9
  import type { ResourceDefinition } from '../../../platform/registry/types'
10
+ import type { WorkflowResourceEntry } from '../../../organization-model/domains/resources'
10
11
  import type { ResourceMetricsConfig } from '../../../operations/observability/types'
11
12
  import type { ExecutionInterface } from '../interface/types'
12
13
 
13
14
  // Workflow configuration
14
15
  export interface WorkflowConfig extends ResourceDefinition {
15
16
  type: 'workflow'
16
- /** Lead-gen capability key for registry derivation (e.g. 'lead-gen.company.apollo-import') */
17
- capabilityKey?: string
17
+ /** OM descriptor backing canonical identity and governance metadata. */
18
+ resource?: WorkflowResourceEntry
19
+ /** Lead-gen action key for registry derivation (e.g. 'lead-gen.company.apollo-import') */
20
+ actionKey?: string
21
+ }
22
+
23
+ export type DescriptorBackedWorkflowConfig = Omit<WorkflowConfig, 'resourceId' | 'type' | 'resource'> & {
24
+ resource: WorkflowResourceEntry
18
25
  }
19
26
 
20
27
  // Workflow step definition
@@ -1,4 +1,4 @@
1
- # @elevasis/core/knowledge
1
+ # @elevasis/core/knowledge
2
2
 
3
3
  Pure query layer over the organization graph. Browser-safe (no Node APIs); shared by the SDK CLI, platform CLI, and the `@elevasis/ui/knowledge` browser.
4
4
 
@@ -6,18 +6,18 @@ Pure query layer over the organization graph. Browser-safe (no Node APIs); share
6
6
 
7
7
  | Export | Purpose |
8
8
  | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
9
- | `byFeature(graph, featureId, knowledgeNodes)` | Knowledge nodes that govern the given feature. |
9
+ | `bySystem(graph, systemId, knowledgeNodes)` | Knowledge nodes that govern the given system. |
10
10
  | `byKind(graph, kind, knowledgeNodes)` | Filter knowledge nodes by `OrgKnowledgeKind`. |
11
11
  | `byOwner(graph, ownerId, knowledgeNodes)` | Knowledge nodes whose `ownerIds` includes the given owner. |
12
- | `governs(graph, nodeId)` | Outgoing `governs` targets (governed-feature ids) from a knowledge node. |
13
- | `governedBy(graph, nodeId)` | Incoming `governs` sources (governing knowledge-node ids) into a feature. |
14
- | `parsePath(pathString)` | Parse `/by-feature/$id`, `/by-kind/$kind`, `/by-owner/$id`, `/graph/$id/{governs,governed-by}`, or `/$id`. Throws on invalid input. |
12
+ | `governs(graph, nodeId)` | Outgoing `governs` targets (governed-system ids) from a knowledge node. |
13
+ | `governedBy(graph, nodeId)` | Incoming `governs` sources (governing knowledge-node ids) into a system. |
14
+ | `parsePath(pathString)` | Parse `/by-system/$id`, `/by-kind/$kind`, `/by-owner/$id`, `/graph/$id/{governs,governed-by}`, or `/$id`. Throws on invalid input. |
15
15
  | `formatText`, `formatJson`, `formatIdsOnly` | Output formatters used by the `knowledge:*` CLI subcommands. |
16
16
 
17
17
  ## Path syntax
18
18
 
19
19
  ```
20
- /by-feature/<featureId>
20
+ /by-system/<systemId>
21
21
  /by-kind/<playbook|strategy|reference>
22
22
  /by-owner/<ownerId>
23
23
  /graph/<nodeId>/governs
@@ -27,6 +27,7 @@ Pure query layer over the organization graph. Browser-safe (no Node APIs); share
27
27
 
28
28
  ## JSON envelope
29
29
 
30
- `formatJson` returns `{ path, mount, args, results }` the same wrapped envelope used by `pnpm exec elevasis knowledge:ls --json` and `pnpm exec elevasis-sdk knowledge:ls --json`.
30
+ `formatJson` returns `{ path, mount, args, results }` — the same wrapped envelope used by `pnpm exec elevasis knowledge:ls --json` and `pnpm exec elevasis-sdk knowledge:ls --json`.
31
31
 
32
32
  `governs` and `governedBy` accept either bare or graph-namespaced ids (`knowledge.foo` or `knowledge:knowledge.foo`).
33
+
@@ -1,5 +1,5 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { byFeature, byKind, byOwner, governs, governedBy, parsePath } from '../queries'
1
+ import { describe, expect, it } from 'vitest'
2
+ import { bySystem, byKind, byOwner, governs, governedBy, parsePath } from '../queries'
3
3
  import { formatText, formatJson, formatIdsOnly } from '../format'
4
4
  import type { OrganizationGraph } from '../../organization-model/graph/types'
5
5
  import type { OrgKnowledgeNode } from '../../organization-model/domains/knowledge'
@@ -10,8 +10,8 @@ import type { OrgKnowledgeNode } from '../../organization-model/domains/knowledg
10
10
 
11
11
  /**
12
12
  * Three knowledge nodes covering all three kinds.
13
- * - playbook-a governs feature:sales.crm and feature:sales.lead-gen
14
- * - strategy-b governs feature:sales.lead-gen
13
+ * - playbook-a governs system:sales.crm and system:sales.lead-gen
14
+ * - strategy-b governs system:sales.lead-gen
15
15
  * - reference-c has no links (no governs edges)
16
16
  * - playbook-d is owned by "role.ops-lead"
17
17
  */
@@ -22,7 +22,7 @@ const NODES: OrgKnowledgeNode[] = [
22
22
  title: 'Playbook A',
23
23
  summary: 'Playbook A summary.',
24
24
  body: '## Playbook A\n\nContent.',
25
- links: [{ nodeId: 'feature:sales.crm' }, { nodeId: 'feature:sales.lead-gen' }],
25
+ links: [{ nodeId: 'system:sales.crm' }, { nodeId: 'system:sales.lead-gen' }],
26
26
  ownerIds: [],
27
27
  updatedAt: '2026-01-01'
28
28
  },
@@ -32,7 +32,7 @@ const NODES: OrgKnowledgeNode[] = [
32
32
  title: 'Strategy B',
33
33
  summary: 'Strategy B summary.',
34
34
  body: '## Strategy B\n\nContent.',
35
- links: [{ nodeId: 'feature:sales.lead-gen' }],
35
+ links: [{ nodeId: 'system:sales.lead-gen' }],
36
36
  ownerIds: ['role.ops-lead'],
37
37
  updatedAt: '2026-01-02'
38
38
  },
@@ -67,13 +67,13 @@ const GRAPH: OrganizationGraph = {
67
67
  organizationModelVersion: 1,
68
68
  nodes: [
69
69
  { id: 'organization-model', kind: 'organization', label: 'Organization Model' },
70
- { id: 'feature:sales.crm', kind: 'feature', label: 'CRM', sourceId: 'sales.crm', featureId: 'sales.crm' },
70
+ { id: 'system:sales.crm', kind: 'system', label: 'CRM', sourceId: 'sales.crm', systemId: 'sales.crm' },
71
71
  {
72
- id: 'feature:sales.lead-gen',
73
- kind: 'feature',
72
+ id: 'system:sales.lead-gen',
73
+ kind: 'system',
74
74
  label: 'Lead Gen',
75
75
  sourceId: 'sales.lead-gen',
76
- featureId: 'sales.lead-gen'
76
+ systemId: 'sales.lead-gen'
77
77
  },
78
78
  {
79
79
  id: 'knowledge:knowledge.playbook-a',
@@ -128,50 +128,50 @@ const GRAPH: OrganizationGraph = {
128
128
  },
129
129
  // governs edges
130
130
  {
131
- id: 'edge:governs:knowledge:knowledge.playbook-a:feature:sales.crm',
131
+ id: 'edge:governs:knowledge:knowledge.playbook-a:system:sales.crm',
132
132
  kind: 'governs',
133
133
  sourceId: 'knowledge:knowledge.playbook-a',
134
- targetId: 'feature:sales.crm'
134
+ targetId: 'system:sales.crm'
135
135
  },
136
136
  {
137
- id: 'edge:governs:knowledge:knowledge.playbook-a:feature:sales.lead-gen',
137
+ id: 'edge:governs:knowledge:knowledge.playbook-a:system:sales.lead-gen',
138
138
  kind: 'governs',
139
139
  sourceId: 'knowledge:knowledge.playbook-a',
140
- targetId: 'feature:sales.lead-gen'
140
+ targetId: 'system:sales.lead-gen'
141
141
  },
142
142
  {
143
- id: 'edge:governs:knowledge:knowledge.strategy-b:feature:sales.lead-gen',
143
+ id: 'edge:governs:knowledge:knowledge.strategy-b:system:sales.lead-gen',
144
144
  kind: 'governs',
145
145
  sourceId: 'knowledge:knowledge.strategy-b',
146
- targetId: 'feature:sales.lead-gen'
146
+ targetId: 'system:sales.lead-gen'
147
147
  }
148
148
  ]
149
149
  }
150
150
 
151
151
  // ---------------------------------------------------------------------------
152
- // byFeature
152
+ // bySystem
153
153
  // ---------------------------------------------------------------------------
154
154
 
155
- describe('byFeature', () => {
156
- it('returns nodes that have governs edges to the given feature', () => {
157
- const result = byFeature(GRAPH, 'sales.crm', NODES)
155
+ describe('bySystem', () => {
156
+ it('returns nodes that have governs edges to the given system', () => {
157
+ const result = bySystem(GRAPH, 'sales.crm', NODES)
158
158
  expect(result).toHaveLength(1)
159
159
  expect(result[0].id).toBe('knowledge.playbook-a')
160
160
  })
161
161
 
162
- it('returns multiple nodes when multiple knowledge nodes govern the same feature', () => {
163
- const result = byFeature(GRAPH, 'sales.lead-gen', NODES)
162
+ it('returns multiple nodes when multiple knowledge nodes govern the same system', () => {
163
+ const result = bySystem(GRAPH, 'sales.lead-gen', NODES)
164
164
  const ids = result.map((n) => n.id).sort()
165
165
  expect(ids).toEqual(['knowledge.playbook-a', 'knowledge.strategy-b'].sort())
166
166
  })
167
167
 
168
- it('returns empty array when no node governs the feature', () => {
169
- const result = byFeature(GRAPH, 'feature.does-not-exist', NODES)
168
+ it('returns empty array when no node governs the system', () => {
169
+ const result = bySystem(GRAPH, 'system.does-not-exist', NODES)
170
170
  expect(result).toHaveLength(0)
171
171
  })
172
172
 
173
173
  it('returns full OrgKnowledgeNode objects (not just graph stubs)', () => {
174
- const result = byFeature(GRAPH, 'sales.crm', NODES)
174
+ const result = bySystem(GRAPH, 'sales.crm', NODES)
175
175
  expect(result[0].body).toBeDefined()
176
176
  expect(result[0].links).toBeDefined()
177
177
  expect(result[0].ownerIds).toBeDefined()
@@ -237,17 +237,17 @@ describe('byOwner', () => {
237
237
  describe('governs', () => {
238
238
  it('returns target graph node IDs for outgoing governs edges (OM node id input)', () => {
239
239
  const result = governs(GRAPH, 'knowledge.playbook-a')
240
- expect(result.sort()).toEqual(['feature:sales.crm', 'feature:sales.lead-gen'].sort())
240
+ expect(result.sort()).toEqual(['system:sales.crm', 'system:sales.lead-gen'].sort())
241
241
  })
242
242
 
243
243
  it('accepts graph node ID format input', () => {
244
244
  const result = governs(GRAPH, 'knowledge:knowledge.playbook-a')
245
- expect(result.sort()).toEqual(['feature:sales.crm', 'feature:sales.lead-gen'].sort())
245
+ expect(result.sort()).toEqual(['system:sales.crm', 'system:sales.lead-gen'].sort())
246
246
  })
247
247
 
248
248
  it('returns single target for a node with one link', () => {
249
249
  const result = governs(GRAPH, 'knowledge.strategy-b')
250
- expect(result).toEqual(['feature:sales.lead-gen'])
250
+ expect(result).toEqual(['system:sales.lead-gen'])
251
251
  })
252
252
 
253
253
  it('returns empty array for a node with no governs edges', () => {
@@ -266,69 +266,69 @@ describe('governs', () => {
266
266
  // ---------------------------------------------------------------------------
267
267
 
268
268
  describe('governedBy', () => {
269
- it('returns knowledge graph node IDs for incoming governs edges (prefixed feature id)', () => {
270
- const result = governedBy(GRAPH, 'feature:sales.lead-gen')
269
+ it('returns knowledge graph node IDs for incoming governs edges (prefixed system id)', () => {
270
+ const result = governedBy(GRAPH, 'system:sales.lead-gen')
271
271
  const sorted = result.sort()
272
272
  expect(sorted).toEqual(['knowledge:knowledge.playbook-a', 'knowledge:knowledge.strategy-b'].sort())
273
273
  })
274
274
 
275
- it('accepts bare feature id (auto-prefixes with feature:)', () => {
275
+ it('accepts bare system id (auto-prefixes with system:)', () => {
276
276
  const result = governedBy(GRAPH, 'sales.crm')
277
277
  expect(result).toEqual(['knowledge:knowledge.playbook-a'])
278
278
  })
279
279
 
280
280
  it('returns empty array when no knowledge node governs the target', () => {
281
- const result = governedBy(GRAPH, 'feature:dashboard')
281
+ const result = governedBy(GRAPH, 'system:dashboard')
282
282
  expect(result).toHaveLength(0)
283
283
  })
284
284
 
285
285
  it('returns empty array for unknown target node id', () => {
286
- const result = governedBy(GRAPH, 'feature:does-not-exist')
286
+ const result = governedBy(GRAPH, 'system:does-not-exist')
287
287
  expect(result).toHaveLength(0)
288
288
  })
289
289
  })
290
290
 
291
291
  // ---------------------------------------------------------------------------
292
- // parsePath happy paths (all five mount axes)
292
+ // parsePath — happy paths (all five mount axes)
293
293
  // ---------------------------------------------------------------------------
294
294
 
295
- describe('parsePath happy paths', () => {
296
- it('/by-kind/playbook mount:by-kind args:[playbook]', () => {
295
+ describe('parsePath — happy paths', () => {
296
+ it('/by-kind/playbook → mount:by-kind args:[playbook]', () => {
297
297
  expect(parsePath('/by-kind/playbook')).toEqual({ mount: 'by-kind', args: ['playbook'] })
298
298
  })
299
299
 
300
- it('/by-kind/strategy mount:by-kind args:[strategy]', () => {
300
+ it('/by-kind/strategy → mount:by-kind args:[strategy]', () => {
301
301
  expect(parsePath('/by-kind/strategy')).toEqual({ mount: 'by-kind', args: ['strategy'] })
302
302
  })
303
303
 
304
- it('/by-feature/sales.crm mount:by-feature args:[sales.crm]', () => {
305
- expect(parsePath('/by-feature/sales.crm')).toEqual({ mount: 'by-feature', args: ['sales.crm'] })
304
+ it('/by-system/sales.crm → mount:by-system args:[sales.crm]', () => {
305
+ expect(parsePath('/by-system/sales.crm')).toEqual({ mount: 'by-system', args: ['sales.crm'] })
306
306
  })
307
307
 
308
- it('/by-feature/sales/crm (slash-delimited) mount:by-feature args:[sales/crm]', () => {
309
- // featureId with slashes is joined and passed as-is for the caller to interpret
310
- expect(parsePath('/by-feature/sales/crm')).toEqual({ mount: 'by-feature', args: ['sales/crm'] })
308
+ it('/by-system/sales/crm (slash-delimited) → mount:by-system args:[sales/crm]', () => {
309
+ // systemId with slashes is joined and passed as-is for the caller to interpret
310
+ expect(parsePath('/by-system/sales/crm')).toEqual({ mount: 'by-system', args: ['sales/crm'] })
311
311
  })
312
312
 
313
- it('/by-owner/role.ops-lead mount:by-owner args:[role.ops-lead]', () => {
313
+ it('/by-owner/role.ops-lead → mount:by-owner args:[role.ops-lead]', () => {
314
314
  expect(parsePath('/by-owner/role.ops-lead')).toEqual({ mount: 'by-owner', args: ['role.ops-lead'] })
315
315
  })
316
316
 
317
- it('/graph/knowledge.outreach-playbook/governs mount:graph args:[knowledge.outreach-playbook,governs]', () => {
317
+ it('/graph/knowledge.outreach-playbook/governs → mount:graph args:[knowledge.outreach-playbook,governs]', () => {
318
318
  expect(parsePath('/graph/knowledge.outreach-playbook/governs')).toEqual({
319
319
  mount: 'graph',
320
320
  args: ['knowledge.outreach-playbook', 'governs']
321
321
  })
322
322
  })
323
323
 
324
- it('/graph/knowledge.outreach-playbook/governed-by mount:graph args:[knowledge.outreach-playbook,governed-by]', () => {
324
+ it('/graph/knowledge.outreach-playbook/governed-by → mount:graph args:[knowledge.outreach-playbook,governed-by]', () => {
325
325
  expect(parsePath('/graph/knowledge.outreach-playbook/governed-by')).toEqual({
326
326
  mount: 'graph',
327
327
  args: ['knowledge.outreach-playbook', 'governed-by']
328
328
  })
329
329
  })
330
330
 
331
- it('/<nodeId> single segment mount:node args:[nodeId]', () => {
331
+ it('/<nodeId> single segment → mount:node args:[nodeId]', () => {
332
332
  expect(parsePath('/knowledge.outreach-playbook')).toEqual({
333
333
  mount: 'node',
334
334
  args: ['knowledge.outreach-playbook']
@@ -341,10 +341,10 @@ describe('parsePath — happy paths', () => {
341
341
  })
342
342
 
343
343
  // ---------------------------------------------------------------------------
344
- // parsePath invalid inputs (5+)
344
+ // parsePath — invalid inputs (5+)
345
345
  // ---------------------------------------------------------------------------
346
346
 
347
- describe('parsePath invalid inputs', () => {
347
+ describe('parsePath — invalid inputs', () => {
348
348
  it('throws on empty string', () => {
349
349
  expect(() => parsePath('')).toThrow('parsePath: path must be a non-empty string')
350
350
  })
@@ -357,8 +357,8 @@ describe('parsePath — invalid inputs', () => {
357
357
  expect(() => parsePath('/')).toThrow('parsePath: path resolves to root with no mount')
358
358
  })
359
359
 
360
- it('throws on /by-feature with no featureId argument', () => {
361
- expect(() => parsePath('/by-feature')).toThrow('/by-feature requires a featureId argument')
360
+ it('throws on /by-system with no systemId argument', () => {
361
+ expect(() => parsePath('/by-system')).toThrow('/by-system requires a systemId argument')
362
362
  })
363
363
 
364
364
  it('throws on /by-kind with no kind argument', () => {
@@ -385,11 +385,11 @@ describe('parsePath — invalid inputs', () => {
385
385
  })
386
386
 
387
387
  // ---------------------------------------------------------------------------
388
- // parsePath edge cases
388
+ // parsePath — edge cases
389
389
  // ---------------------------------------------------------------------------
390
390
 
391
391
  describe('parsePath edge cases', () => {
392
- // Whitespace handling the parser does NOT trim the incoming string.
392
+ // Whitespace handling — the parser does NOT trim the incoming string.
393
393
  // A path with leading whitespace does not start with '/' so it throws.
394
394
  it('throws on path with leading whitespace', () => {
395
395
  expect(() => parsePath(' /by-kind/playbook')).toThrow('parsePath: path must start with "/"')
@@ -409,13 +409,13 @@ describe('parsePath edge cases', () => {
409
409
  expect(parsePath('/by-kind/playbook///')).toEqual({ mount: 'by-kind', args: ['playbook'] })
410
410
  })
411
411
 
412
- // Lone-slash variants a path of only slashes normalises to an empty
412
+ // Lone-slash variants — a path of only slashes normalises to an empty
413
413
  // segments list and must throw the root-with-no-mount error.
414
414
  it('throws on path consisting only of slashes', () => {
415
415
  expect(() => parsePath('///')).toThrow('parsePath: path resolves to root with no mount')
416
416
  })
417
417
 
418
- // Case sensitivity parsePath does not normalise case.
418
+ // Case sensitivity — parsePath does not normalise case.
419
419
  // 'Playbook' (capitalised) is passed through as-is to the caller.
420
420
  it('preserves node-id case (no case normalisation)', () => {
421
421
  // /by-kind takes rest[0] verbatim
@@ -423,16 +423,16 @@ describe('parsePath edge cases', () => {
423
423
  expect(result.args[0]).toBe('Playbook')
424
424
  })
425
425
 
426
- // Feature ID: dot notation passes through unchanged.
427
- it('/by-feature/sales.crm preserves dot notation', () => {
428
- expect(parsePath('/by-feature/sales.crm')).toEqual({ mount: 'by-feature', args: ['sales.crm'] })
426
+ // system id: dot notation passes through unchanged.
427
+ it('/by-system/sales.crm preserves dot notation', () => {
428
+ expect(parsePath('/by-system/sales.crm')).toEqual({ mount: 'by-system', args: ['sales.crm'] })
429
429
  })
430
430
 
431
- // Feature ID: slash notation joins rest segments with '/'.
432
- // The parser does NOT convert slashes to dots the caller must normalise.
433
- it('/by-feature/sales/crm joins segments with slash (no dot conversion)', () => {
434
- const result = parsePath('/by-feature/sales/crm')
435
- expect(result.mount).toBe('by-feature')
431
+ // system id: slash notation joins rest segments with '/'.
432
+ // The parser does NOT convert slashes to dots — the caller must normalise.
433
+ it('/by-system/sales/crm joins segments with slash (no dot conversion)', () => {
434
+ const result = parsePath('/by-system/sales/crm')
435
+ expect(result.mount).toBe('by-system')
436
436
  // args[0] is 'sales/crm', NOT 'sales.crm'
437
437
  expect(result.args[0]).toBe('sales/crm')
438
438
  })
@@ -446,7 +446,7 @@ describe('parsePath edge cases', () => {
446
446
  expect(result.args).toHaveLength(1)
447
447
  })
448
448
 
449
- // /by-owner joins rest with '/' (same behaviour as /by-feature).
449
+ // /by-owner joins rest with '/' (same behaviour as /by-system).
450
450
  it('/by-owner/role.ops-lead/sub joins segments with slash', () => {
451
451
  const result = parsePath('/by-owner/role.ops-lead/sub')
452
452
  expect(result.mount).toBe('by-owner')
@@ -454,7 +454,7 @@ describe('parsePath edge cases', () => {
454
454
  })
455
455
 
456
456
  // Graph node ID may contain a colon prefix (e.g. knowledge:knowledge.foo).
457
- // The parser accepts it the rest before the verb is joined with '/'.
457
+ // The parser accepts it — the rest before the verb is joined with '/'.
458
458
  it('/graph/<prefixed-nodeId>/governs round-trips the prefixed id', () => {
459
459
  const result = parsePath('/graph/knowledge:knowledge.test-node/governs')
460
460
  expect(result.mount).toBe('graph')
@@ -467,7 +467,7 @@ describe('parsePath edge cases', () => {
467
467
  expect(() => parsePath('/graph')).toThrow('/graph requires <nodeId>/<verb>')
468
468
  })
469
469
 
470
- // /node mount single-segment path works for any non-reserved first segment.
470
+ // /node mount — single-segment path works for any non-reserved first segment.
471
471
  it('single-segment non-reserved path is treated as node mount', () => {
472
472
  expect(parsePath('/knowledge.my-doc')).toEqual({ mount: 'node', args: ['knowledge.my-doc'] })
473
473
  })
@@ -504,7 +504,7 @@ describe('formatText', () => {
504
504
  })
505
505
 
506
506
  // ---------------------------------------------------------------------------
507
- // formatJson envelope shape
507
+ // formatJson — envelope shape
508
508
  // ---------------------------------------------------------------------------
509
509
 
510
510
  describe('formatJson', () => {
@@ -538,8 +538,8 @@ describe('formatJson', () => {
538
538
 
539
539
  it('preserves args array in envelope', () => {
540
540
  const raw = formatJson({
541
- path: '/by-feature/sales.crm',
542
- parsed: { mount: 'by-feature', args: ['sales.crm'] },
541
+ path: '/by-system/sales.crm',
542
+ parsed: { mount: 'by-system', args: ['sales.crm'] },
543
543
  results: []
544
544
  })
545
545
  expect(JSON.parse(raw)).toMatchObject({ args: ['sales.crm'] })
@@ -549,13 +549,13 @@ describe('formatJson', () => {
549
549
  const raw = formatJson({
550
550
  path: '/graph/knowledge.playbook-a/governs',
551
551
  parsed: { mount: 'graph', args: ['knowledge.playbook-a', 'governs'] },
552
- results: ['feature:sales.crm', 'feature:sales.lead-gen']
552
+ results: ['system:sales.crm', 'system:sales.lead-gen']
553
553
  })
554
554
  const parsed = JSON.parse(raw) as { results: string[] }
555
- expect(parsed.results).toEqual(['feature:sales.crm', 'feature:sales.lead-gen'])
555
+ expect(parsed.results).toEqual(['system:sales.crm', 'system:sales.lead-gen'])
556
556
  })
557
557
 
558
- it('envelope is NOT flat { results } must have path/mount/args siblings', () => {
558
+ it('envelope is NOT flat { results } — must have path/mount/args siblings', () => {
559
559
  const raw = formatJson({
560
560
  path: '/by-kind/playbook',
561
561
  parsed: { mount: 'by-kind', args: ['playbook'] },
@@ -587,7 +587,8 @@ describe('formatIdsOnly', () => {
587
587
  })
588
588
 
589
589
  it('returns newline-separated strings for string[] (governs results)', () => {
590
- const output = formatIdsOnly(['feature:sales.crm', 'feature:sales.lead-gen'])
591
- expect(output).toBe('feature:sales.crm\nfeature:sales.lead-gen')
590
+ const output = formatIdsOnly(['system:sales.crm', 'system:sales.lead-gen'])
591
+ expect(output).toBe('system:sales.crm\nsystem:sales.lead-gen')
592
592
  })
593
593
  })
594
+