@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.
- package/dist/index.d.ts +2518 -2169
- package/dist/index.js +2495 -1095
- package/dist/knowledge/index.d.ts +706 -1044
- package/dist/knowledge/index.js +9 -9
- package/dist/organization-model/index.d.ts +2518 -2169
- package/dist/organization-model/index.js +2495 -1095
- package/dist/test-utils/index.d.ts +826 -1014
- package/dist/test-utils/index.js +1894 -1032
- package/package.json +3 -3
- package/src/__tests__/template-core-compatibility.test.ts +11 -79
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +852 -397
- package/src/auth/multi-tenancy/permissions.ts +20 -8
- package/src/business/README.md +2 -2
- package/src/business/acquisition/api-schemas.test.ts +175 -2
- package/src/business/acquisition/api-schemas.ts +132 -16
- package/src/business/acquisition/build-templates.test.ts +4 -4
- package/src/business/acquisition/build-templates.ts +72 -30
- package/src/business/acquisition/crm-state-actions.test.ts +13 -11
- package/src/business/acquisition/index.ts +12 -0
- package/src/business/acquisition/types.ts +7 -3
- package/src/business/clients/api-schemas.test.ts +115 -0
- package/src/business/clients/api-schemas.ts +158 -0
- package/src/business/clients/index.ts +1 -0
- package/src/business/deals/api-schemas.ts +8 -0
- package/src/business/index.ts +5 -2
- package/src/business/projects/types.ts +19 -0
- package/src/execution/engine/__tests__/fixtures/test-agents.ts +10 -8
- package/src/execution/engine/agent/core/__tests__/agent.test.ts +16 -12
- package/src/execution/engine/agent/core/__tests__/error-passthrough.test.ts +4 -3
- package/src/execution/engine/agent/core/types.ts +25 -15
- package/src/execution/engine/agent/index.ts +6 -4
- package/src/execution/engine/agent/reasoning/__tests__/request-builder.test.ts +24 -18
- package/src/execution/engine/index.ts +3 -0
- package/src/execution/engine/workflow/types.ts +9 -2
- package/src/knowledge/README.md +8 -7
- package/src/knowledge/__tests__/queries.test.ts +74 -73
- package/src/knowledge/format.ts +10 -9
- package/src/knowledge/index.ts +1 -1
- package/src/knowledge/published.ts +1 -1
- package/src/knowledge/queries.ts +26 -25
- package/src/organization-model/README.md +73 -26
- package/src/organization-model/__tests__/content-kinds-registry.test.ts +210 -0
- package/src/organization-model/__tests__/defaults.test.ts +76 -96
- package/src/organization-model/__tests__/domains/actions.test.ts +56 -0
- package/src/organization-model/__tests__/domains/customers.test.ts +299 -295
- package/src/organization-model/__tests__/domains/entities.test.ts +56 -0
- package/src/organization-model/__tests__/domains/goals.test.ts +493 -479
- package/src/organization-model/__tests__/domains/identity.test.ts +280 -279
- package/src/organization-model/__tests__/domains/navigation.test.ts +268 -212
- package/src/organization-model/__tests__/domains/offerings.test.ts +414 -419
- package/src/organization-model/__tests__/domains/policies.test.ts +323 -0
- package/src/organization-model/__tests__/domains/resource-mappings.test.ts +271 -271
- package/src/organization-model/__tests__/domains/resources.test.ts +310 -0
- package/src/organization-model/__tests__/domains/roles.test.ts +463 -347
- package/src/organization-model/__tests__/domains/statuses.test.ts +246 -243
- package/src/organization-model/__tests__/domains/systems.test.ts +209 -0
- package/src/organization-model/__tests__/flatten-additive-merge.test.ts +361 -0
- package/src/organization-model/__tests__/foundation.test.ts +74 -102
- package/src/organization-model/__tests__/get-resources-for-system.test.ts +144 -0
- package/src/organization-model/__tests__/graph.test.ts +899 -71
- package/src/organization-model/__tests__/knowledge.test.ts +209 -49
- package/src/organization-model/__tests__/lookup-helpers.test.ts +438 -0
- package/src/organization-model/__tests__/migration-helpers.test.ts +591 -0
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +36 -27
- package/src/organization-model/__tests__/recursive-system-schema.test.ts +520 -0
- package/src/organization-model/__tests__/resolve.test.ts +174 -23
- package/src/organization-model/__tests__/schema.test.ts +291 -114
- package/src/organization-model/__tests__/surface-projection.test.ts +207 -97
- package/src/organization-model/catalogs/lead-gen.ts +144 -0
- package/src/organization-model/content-kinds/config.ts +36 -0
- package/src/organization-model/content-kinds/index.ts +74 -0
- package/src/organization-model/content-kinds/pipeline.ts +68 -0
- package/src/organization-model/content-kinds/registry.ts +44 -0
- package/src/organization-model/content-kinds/status.ts +71 -0
- package/src/organization-model/content-kinds/template.ts +83 -0
- package/src/organization-model/content-kinds/types.ts +117 -0
- package/src/organization-model/contracts.ts +13 -3
- package/src/organization-model/defaults.ts +499 -86
- package/src/organization-model/domains/actions.ts +239 -0
- package/src/organization-model/domains/customers.ts +78 -75
- package/src/organization-model/domains/entities.ts +144 -0
- package/src/organization-model/domains/goals.ts +83 -80
- package/src/organization-model/domains/knowledge.ts +76 -17
- package/src/organization-model/domains/navigation.ts +107 -384
- package/src/organization-model/domains/offerings.ts +71 -66
- package/src/organization-model/domains/policies.ts +102 -0
- package/src/organization-model/domains/projects.ts +14 -48
- package/src/organization-model/domains/prospecting.ts +62 -181
- package/src/organization-model/domains/resources.ts +145 -0
- package/src/organization-model/domains/roles.ts +96 -55
- package/src/organization-model/domains/sales.ts +10 -219
- package/src/organization-model/domains/shared.ts +57 -57
- package/src/organization-model/domains/statuses.ts +339 -130
- package/src/organization-model/domains/systems.ts +203 -0
- package/src/organization-model/foundation.ts +54 -67
- package/src/organization-model/graph/build.ts +682 -54
- package/src/organization-model/graph/link.ts +1 -1
- package/src/organization-model/graph/schema.ts +24 -9
- package/src/organization-model/graph/types.ts +20 -7
- package/src/organization-model/helpers.ts +231 -26
- package/src/organization-model/icons.ts +1 -0
- package/src/organization-model/index.ts +118 -5
- package/src/organization-model/migration-helpers.ts +249 -0
- package/src/organization-model/organization-graph.mdx +16 -15
- package/src/organization-model/organization-model.mdx +111 -44
- package/src/organization-model/published.ts +172 -19
- package/src/organization-model/resolve.ts +117 -54
- package/src/organization-model/schema.ts +654 -112
- package/src/organization-model/surface-projection.ts +116 -122
- package/src/organization-model/types.ts +146 -20
- package/src/platform/api/types.ts +38 -35
- package/src/platform/constants/versions.ts +1 -1
- package/src/platform/registry/__tests__/command-view.test.ts +6 -8
- package/src/platform/registry/__tests__/resource-link.test.ts +13 -8
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +16 -31
- package/src/platform/registry/__tests__/resource-registry.nested-systems.test.ts +245 -0
- package/src/platform/registry/__tests__/resource-registry.test.ts +2053 -2005
- package/src/platform/registry/__tests__/validation.test.ts +1347 -1086
- package/src/platform/registry/index.ts +14 -0
- package/src/platform/registry/resource-registry.ts +52 -2
- package/src/platform/registry/serialization.ts +241 -202
- package/src/platform/registry/serialized-types.ts +1 -0
- package/src/platform/registry/types.ts +411 -361
- package/src/platform/registry/validation.ts +745 -513
- package/src/projects/api-schemas.ts +290 -267
- package/src/reference/_generated/contracts.md +853 -397
- package/src/reference/glossary.md +23 -18
- package/src/supabase/database.types.ts +181 -0
- package/src/test-utils/test-utils.test.ts +1 -6
- package/src/organization-model/__tests__/domains/operations.test.ts +0 -203
- package/src/organization-model/domains/features.ts +0 -31
- package/src/organization-model/domains/operations.ts +0 -85
package/src/knowledge/format.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OrgKnowledgeNode } from '../organization-model/domains/knowledge'
|
|
1
|
+
import type { OrgKnowledgeNode } from '../organization-model/domains/knowledge'
|
|
2
2
|
import type { KnowledgeMount, ParsedKnowledgePath } from './queries'
|
|
3
3
|
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
@@ -9,7 +9,7 @@ import type { KnowledgeMount, ParsedKnowledgePath } from './queries'
|
|
|
9
9
|
* Renders a list of `OrgKnowledgeNode` results as a human-friendly text table.
|
|
10
10
|
*
|
|
11
11
|
* Output format (one row per node):
|
|
12
|
-
* `<kind> <id> <title>
|
|
12
|
+
* `<kind> <id> <title> — <summary (truncated to 80 chars)>`
|
|
13
13
|
*
|
|
14
14
|
* Returns `"(no results)"` when the array is empty.
|
|
15
15
|
*/
|
|
@@ -26,7 +26,7 @@ export function formatText(results: OrgKnowledgeNode[]): string {
|
|
|
26
26
|
|
|
27
27
|
const rows = results.map((n) => {
|
|
28
28
|
const summary = n.summary.length > 80 ? n.summary.slice(0, 77) + '...' : n.summary
|
|
29
|
-
return `${n.kind.padEnd(kindWidth)} ${n.id.padEnd(idWidth)} ${n.title}
|
|
29
|
+
return `${n.kind.padEnd(kindWidth)} ${n.id.padEnd(idWidth)} ${n.title} — ${summary}`
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
return [header, divider, ...rows].join('\n')
|
|
@@ -41,10 +41,10 @@ export function formatText(results: OrgKnowledgeNode[]): string {
|
|
|
41
41
|
*
|
|
42
42
|
* Shape: `{ path, mount, args, results }`
|
|
43
43
|
*
|
|
44
|
-
* - `path`
|
|
45
|
-
* - `mount`
|
|
46
|
-
* - `args`
|
|
47
|
-
* - `results`
|
|
44
|
+
* - `path` — the original path string passed to `parsePath`
|
|
45
|
+
* - `mount` — the resolved mount axis
|
|
46
|
+
* - `args` — the parsed arguments array
|
|
47
|
+
* - `results` — the query results (array of `OrgKnowledgeNode` or string IDs)
|
|
48
48
|
*/
|
|
49
49
|
export interface KnowledgeJsonEnvelope {
|
|
50
50
|
path: string
|
|
@@ -57,10 +57,10 @@ export interface KnowledgeJsonEnvelope {
|
|
|
57
57
|
* Formats query results as a wrapped JSON envelope string.
|
|
58
58
|
*
|
|
59
59
|
* The envelope shape is `{ path, mount, args, results }`. This is intentionally
|
|
60
|
-
* NOT flat `{ results }`
|
|
60
|
+
* NOT flat `{ results }` — consumers (agent skills, jq pipelines) need the
|
|
61
61
|
* mount + args to know how to interpret the results array.
|
|
62
62
|
*
|
|
63
|
-
* @param input.path - The original path string (e.g. `"/by-
|
|
63
|
+
* @param input.path - The original path string (e.g. `"/by-system/sales.crm"`).
|
|
64
64
|
* @param input.mount - The resolved mount axis.
|
|
65
65
|
* @param input.args - The parsed argument array.
|
|
66
66
|
* @param input.results - The query results.
|
|
@@ -97,3 +97,4 @@ export function formatIdsOnly(results: OrgKnowledgeNode[] | string[]): string {
|
|
|
97
97
|
const ids = results.map((r) => (typeof r === 'string' ? r : r.id))
|
|
98
98
|
return ids.join('\n')
|
|
99
99
|
}
|
|
100
|
+
|
package/src/knowledge/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { bySystem, byKind, byOwner, governs, governedBy, parsePath } from './queries'
|
|
2
2
|
export type { KnowledgeMount, ParsedKnowledgePath } from './queries'
|
|
3
3
|
|
|
4
4
|
export { formatText, formatJson, formatIdsOnly } from './format'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { bySystem, byKind, byOwner, governs, governedBy, parsePath } from './queries'
|
|
2
2
|
export type { KnowledgeMount, ParsedKnowledgePath } from './queries'
|
|
3
3
|
|
|
4
4
|
export { formatText, formatJson, formatIdsOnly } from './format'
|
package/src/knowledge/queries.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OrganizationGraph } from '../organization-model/graph/types'
|
|
1
|
+
import type { OrganizationGraph } from '../organization-model/graph/types'
|
|
2
2
|
import type { OrgKnowledgeKind, OrgKnowledgeNode } from '../organization-model/domains/knowledge'
|
|
3
3
|
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
@@ -35,17 +35,17 @@ function buildKnowledgeSourceIdMap(graph: OrganizationGraph): Map<string, string
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Returns all knowledge nodes whose `governs` edges point to the given
|
|
38
|
-
*
|
|
38
|
+
* systemId (graph node ID: `system:<systemId>`).
|
|
39
39
|
*
|
|
40
40
|
* @param graph - The built OrganizationGraph.
|
|
41
|
-
* @param
|
|
41
|
+
* @param systemId - The dotted system id (e.g. `sales.crm`).
|
|
42
42
|
*/
|
|
43
|
-
export function
|
|
43
|
+
export function bySystem(
|
|
44
44
|
graph: OrganizationGraph,
|
|
45
|
-
|
|
45
|
+
systemId: string,
|
|
46
46
|
knowledgeNodes: OrgKnowledgeNode[]
|
|
47
47
|
): OrgKnowledgeNode[] {
|
|
48
|
-
const targetGraphNodeId = `
|
|
48
|
+
const targetGraphNodeId = `system:${systemId}`
|
|
49
49
|
|
|
50
50
|
// Find all knowledge graph node IDs that have a `governs` edge to the target
|
|
51
51
|
const governingKnowledgeNodeIds = new Set<string>()
|
|
@@ -107,7 +107,7 @@ export function byOwner(
|
|
|
107
107
|
* @param graph - The built OrganizationGraph.
|
|
108
108
|
* @param nodeId - The OM knowledge node id (e.g. `knowledge.outreach-playbook`).
|
|
109
109
|
* Also accepts the graph node ID format (`knowledge:knowledge.outreach-playbook`).
|
|
110
|
-
* @returns Array of target graph node IDs (e.g. `['
|
|
110
|
+
* @returns Array of target graph node IDs (e.g. `['system:sales.crm', ...]`).
|
|
111
111
|
*/
|
|
112
112
|
export function governs(graph: OrganizationGraph, nodeId: string): string[] {
|
|
113
113
|
const graphNodeId = nodeId.startsWith('knowledge:') ? nodeId : toGraphNodeId(nodeId)
|
|
@@ -126,16 +126,16 @@ export function governs(graph: OrganizationGraph, nodeId: string): string[] {
|
|
|
126
126
|
* (incoming `governs` edges pointing to `nodeId`).
|
|
127
127
|
*
|
|
128
128
|
* @param graph - The built OrganizationGraph.
|
|
129
|
-
* @param nodeId - The target graph node ID (e.g. `
|
|
130
|
-
* bare
|
|
129
|
+
* @param nodeId - The target graph node ID (e.g. `system:sales.crm`) or a
|
|
130
|
+
* bare system id (e.g. `sales.crm` — will be prefixed with `system:`).
|
|
131
131
|
* @returns Array of source graph node IDs (e.g. `['knowledge:knowledge.outreach-playbook', ...]`).
|
|
132
132
|
*/
|
|
133
133
|
export function governedBy(graph: OrganizationGraph, nodeId: string): string[] {
|
|
134
|
-
// Accept both `
|
|
134
|
+
// Accept both `system:sales.crm` and bare `sales.crm`
|
|
135
135
|
const targetId =
|
|
136
|
-
nodeId.startsWith('
|
|
136
|
+
nodeId.startsWith('system:') || nodeId.startsWith('knowledge:') || nodeId.startsWith('resource:')
|
|
137
137
|
? nodeId
|
|
138
|
-
: `
|
|
138
|
+
: `system:${nodeId}`
|
|
139
139
|
|
|
140
140
|
const results: string[] = []
|
|
141
141
|
for (const edge of graph.edges) {
|
|
@@ -151,7 +151,7 @@ export function governedBy(graph: OrganizationGraph, nodeId: string): string[] {
|
|
|
151
151
|
// ---------------------------------------------------------------------------
|
|
152
152
|
|
|
153
153
|
/** The recognized mount axes for Knowledge Map paths. */
|
|
154
|
-
export type KnowledgeMount = 'by-
|
|
154
|
+
export type KnowledgeMount = 'by-system' | 'by-kind' | 'by-owner' | 'graph' | 'node'
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
157
|
* The result of parsing a Knowledge Map path string.
|
|
@@ -159,7 +159,7 @@ export type KnowledgeMount = 'by-feature' | 'by-kind' | 'by-owner' | 'graph' | '
|
|
|
159
159
|
* Shape: `{ mount: KnowledgeMount, args: string[] }`
|
|
160
160
|
*
|
|
161
161
|
* Per-mount arg arrays:
|
|
162
|
-
* - `by-
|
|
162
|
+
* - `by-system`: `[systemId]` (e.g. `['sales.crm']`)
|
|
163
163
|
* - `by-kind`: `[kind]` (e.g. `['playbook']`)
|
|
164
164
|
* - `by-owner`: `[ownerId]` (e.g. `['role.ops-lead']`)
|
|
165
165
|
* - `graph`: `[nodeId, verb]` where verb is `'governs'` or `'governed-by'`
|
|
@@ -174,12 +174,12 @@ export interface ParsedKnowledgePath {
|
|
|
174
174
|
* Parses a Knowledge Map path string into a `{ mount, args }` descriptor.
|
|
175
175
|
*
|
|
176
176
|
* Supported path patterns:
|
|
177
|
-
* `/by-
|
|
178
|
-
* `/by-kind/<kind>`
|
|
179
|
-
* `/by-owner/<ownerId>`
|
|
180
|
-
* `/graph/<nodeId>/governs`
|
|
181
|
-
* `/graph/<nodeId>/governed-by`
|
|
182
|
-
* `/<nodeId>`
|
|
177
|
+
* `/by-system/<systemId>` -> `{ mount: 'by-system', args: ['<systemId>'] }`
|
|
178
|
+
* `/by-kind/<kind>` → `{ mount: 'by-kind', args: ['<kind>'] }`
|
|
179
|
+
* `/by-owner/<ownerId>` → `{ mount: 'by-owner', args: ['<ownerId>'] }`
|
|
180
|
+
* `/graph/<nodeId>/governs` → `{ mount: 'graph', args: ['<nodeId>', 'governs'] }`
|
|
181
|
+
* `/graph/<nodeId>/governed-by` → `{ mount: 'graph', args: ['<nodeId>', 'governed-by'] }`
|
|
182
|
+
* `/<nodeId>` → `{ mount: 'node', args: ['<nodeId>'] }`
|
|
183
183
|
*
|
|
184
184
|
* The path MUST start with `/`. Trailing slashes are stripped before parsing.
|
|
185
185
|
*
|
|
@@ -205,12 +205,12 @@ export function parsePath(pathString: string): ParsedKnowledgePath {
|
|
|
205
205
|
|
|
206
206
|
const [first, ...rest] = segments
|
|
207
207
|
|
|
208
|
-
// /by-
|
|
209
|
-
if (first === 'by-
|
|
208
|
+
// /by-system/<systemId>
|
|
209
|
+
if (first === 'by-system') {
|
|
210
210
|
if (rest.length === 0) {
|
|
211
|
-
throw new Error(`parsePath: /by-
|
|
211
|
+
throw new Error(`parsePath: /by-system requires a systemId argument, got: "${pathString}"`)
|
|
212
212
|
}
|
|
213
|
-
return { mount: 'by-
|
|
213
|
+
return { mount: 'by-system', args: [rest.join('/')] }
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
// /by-kind/<kind>
|
|
@@ -251,6 +251,7 @@ export function parsePath(pathString: string): ParsedKnowledgePath {
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
throw new Error(
|
|
254
|
-
`parsePath: unrecognized path pattern "${pathString}". Supported: /by-
|
|
254
|
+
`parsePath: unrecognized path pattern "${pathString}". Supported: /by-system/<id>, /by-kind/<kind>, /by-owner/<id>, /graph/<nodeId>/governs, /graph/<nodeId>/governed-by, /<nodeId>`
|
|
255
255
|
)
|
|
256
256
|
}
|
|
257
|
+
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
# Organization Model
|
|
2
2
|
|
|
3
|
-
The organization model is the published semantic contract that maps a tenant's
|
|
3
|
+
The organization model is the published semantic contract that maps a tenant's System hierarchy, shell navigation, business semantics, resource governance, and graph bindings.
|
|
4
4
|
|
|
5
|
-
Use this module when resolving or validating an organization's contract before wiring UI shells, routing,
|
|
5
|
+
Use this module when resolving or validating an organization's contract before wiring UI shells, routing, system gates, or domain-specific capability checks.
|
|
6
6
|
|
|
7
7
|
## Published Exports
|
|
8
8
|
|
|
9
9
|
The public entry point exposes:
|
|
10
10
|
|
|
11
11
|
- `OrganizationModelSchema`
|
|
12
|
-
- `FeatureSchema`
|
|
13
12
|
- `DEFAULT_ORGANIZATION_MODEL`
|
|
14
13
|
- `defineOrganizationModel`
|
|
15
14
|
- `resolveOrganizationModel`
|
|
16
15
|
- `createFoundationOrganizationModel`
|
|
17
|
-
- node ID and
|
|
16
|
+
- node ID and System helper types
|
|
18
17
|
- `OrganizationModel` and supporting domain types
|
|
19
18
|
|
|
20
19
|
Import it from the published subpath:
|
|
@@ -36,8 +35,8 @@ The model is versioned and currently validates against `version: 1`.
|
|
|
36
35
|
Top-level fields:
|
|
37
36
|
|
|
38
37
|
- `version`
|
|
38
|
+
- `domainMetadata`
|
|
39
39
|
- `branding`
|
|
40
|
-
- `features`
|
|
41
40
|
- `navigation`
|
|
42
41
|
- `sales`
|
|
43
42
|
- `prospecting`
|
|
@@ -47,35 +46,83 @@ Top-level fields:
|
|
|
47
46
|
- `offerings`
|
|
48
47
|
- `roles`
|
|
49
48
|
- `goals`
|
|
49
|
+
- `systems`
|
|
50
|
+
- `resources`
|
|
51
|
+
- `capabilities`
|
|
52
|
+
- `policies`
|
|
50
53
|
- `statuses`
|
|
51
|
-
- `
|
|
54
|
+
- `knowledge`
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
The pure collection domains are id-keyed maps: `systems`, `roles`, `goals`, `customers`, `offerings`, `resources`, `capabilities`, `policies`, and `statuses`. The map key must match the entry `id`. Entries carry `order` for deterministic ordered views; use `listDomain(record)` when order matters.
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
Resource identity is authored in `resources`. Runtime workflows, agents, integrations, and scripts import those descriptors, derive `resourceId` and kind from them, and attach executable behavior in operations code.
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
## System Set
|
|
61
|
+
|
|
62
|
+
System hierarchy is authored as an id-keyed `systems` map. Dotted IDs and `parentSystemId` define parent/child relationships:
|
|
58
63
|
|
|
59
64
|
```ts
|
|
60
|
-
|
|
61
|
-
{ id: 'dashboard',
|
|
62
|
-
{ id: 'sales', label: 'Sales',
|
|
63
|
-
{ id: '
|
|
64
|
-
{ id: '
|
|
65
|
-
|
|
65
|
+
systems: {
|
|
66
|
+
dashboard: { id: 'dashboard', order: 10, label: 'Dashboard', lifecycle: 'active' },
|
|
67
|
+
sales: { id: 'sales', order: 20, label: 'Sales', lifecycle: 'active' },
|
|
68
|
+
clients: { id: 'clients', order: 30, label: 'Clients', lifecycle: 'active' },
|
|
69
|
+
projects: { id: 'projects', order: 40, label: 'Projects', lifecycle: 'active' }
|
|
70
|
+
}
|
|
66
71
|
```
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
Systems describe semantic ownership, hierarchy, lifecycle, access, and governance. Sidebar presentation is authored separately under `navigation.sidebar`; UI-backed Systems may still provide `ui.path` for route matching during the migration, but they do not author composed surface lists. Lifecycle values such as `active`, `beta`, `deprecated`, and `archived` replace the old feature enabled/dev-only split.
|
|
74
|
+
|
|
75
|
+
## Sidebar Navigation
|
|
76
|
+
|
|
77
|
+
Shell navigation is authored as a recursive sidebar tree:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
navigation: {
|
|
81
|
+
sidebar: {
|
|
82
|
+
primary: {
|
|
83
|
+
dashboard: {
|
|
84
|
+
type: 'surface',
|
|
85
|
+
order: 10,
|
|
86
|
+
label: 'Dashboard',
|
|
87
|
+
path: '/',
|
|
88
|
+
surfaceType: 'dashboard',
|
|
89
|
+
targets: { systems: ['dashboard'] }
|
|
90
|
+
},
|
|
91
|
+
business: {
|
|
92
|
+
type: 'group',
|
|
93
|
+
order: 20,
|
|
94
|
+
label: 'Business',
|
|
95
|
+
children: {
|
|
96
|
+
clients: {
|
|
97
|
+
type: 'surface',
|
|
98
|
+
order: 20,
|
|
99
|
+
label: 'Clients',
|
|
100
|
+
path: '/clients',
|
|
101
|
+
surfaceType: 'list',
|
|
102
|
+
targets: { systems: ['clients'] }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
bottom: {}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Routeable sidebar leaves are projected into flat semantic surface DTOs by `projectOrganizationSurfaces(model)`. Do not author top-level `surfaces` or `navigationGroups` fields on `OrganizationModel`.
|
|
70
113
|
|
|
71
114
|
## Graph IDs
|
|
72
115
|
|
|
73
116
|
Cross-collection links use kind-prefixed IDs:
|
|
74
117
|
|
|
75
|
-
- `
|
|
118
|
+
- `system:sales.crm`
|
|
76
119
|
- `integration:instantly`
|
|
77
120
|
- `resource:lead-import`
|
|
78
|
-
- `
|
|
121
|
+
- `action:operations.queue.review`
|
|
122
|
+
|
|
123
|
+
## Resource Descriptors
|
|
124
|
+
|
|
125
|
+
The OM Resources domain is governance-only. Descriptors declare canonical `id`, required `systemPath`, governance `status`, and optional role ownership. `DeploymentSpec` remains the runtime/deploy assembly around those descriptors, not a second resource identity catalog.
|
|
79
126
|
|
|
80
127
|
## Resolution Semantics
|
|
81
128
|
|
|
@@ -83,20 +130,20 @@ Cross-collection links use kind-prefixed IDs:
|
|
|
83
130
|
- `resolveOrganizationModel()` deep-merges a partial override into the default model, then validates it.
|
|
84
131
|
- `createFoundationOrganizationModel()` resolves the canonical model and returns UI-facing helper outputs.
|
|
85
132
|
- Plain objects merge recursively.
|
|
133
|
+
- Id-keyed domain maps merge additively by key.
|
|
86
134
|
- Arrays replace the default value.
|
|
87
135
|
- Missing fields fall back to `DEFAULT_ORGANIZATION_MODEL`.
|
|
88
136
|
|
|
89
137
|
## Referential Integrity
|
|
90
138
|
|
|
91
|
-
-
|
|
92
|
-
- Child
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
- Resource links are validated against graph node IDs during registry registration.
|
|
139
|
+
- System IDs must be unique.
|
|
140
|
+
- Child System IDs require valid ancestors or `parentSystemId` links.
|
|
141
|
+
- Systems with UI provide `ui.path`.
|
|
142
|
+
- Systems, resources, roles, knowledge nodes, and goals must resolve their declared cross-references.
|
|
96
143
|
|
|
97
144
|
## Practical Guidance
|
|
98
145
|
|
|
99
146
|
- Use `resolveOrganizationModel()` when you need a runtime-safe model.
|
|
100
147
|
- Use `defineOrganizationModel()` when authoring static overrides.
|
|
101
|
-
- Keep
|
|
102
|
-
- Put
|
|
148
|
+
- Keep System IDs stable because shell routing, gating, breadcrumbs, and docs depend on them.
|
|
149
|
+
- Put resource identity and governance in `resources`; attach executable behavior in operations.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
CONTENT_KIND_REGISTRY,
|
|
4
|
+
defineContentType,
|
|
5
|
+
lookupContentType,
|
|
6
|
+
pipelineKind,
|
|
7
|
+
stageKind,
|
|
8
|
+
templateKind,
|
|
9
|
+
templateStepKind,
|
|
10
|
+
statusFlowKind,
|
|
11
|
+
statusKind,
|
|
12
|
+
configKvKind
|
|
13
|
+
} from '../content-kinds/index'
|
|
14
|
+
import type { ContentTypeDefinition, ContentTypeKey } from '../content-kinds/types'
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// CONTENT_KIND_REGISTRY shape (D8: static object literal)
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
describe('CONTENT_KIND_REGISTRY', () => {
|
|
21
|
+
it('contains all 7 expected keys', () => {
|
|
22
|
+
const expectedKeys: ContentTypeKey[] = [
|
|
23
|
+
'schema:pipeline',
|
|
24
|
+
'schema:stage',
|
|
25
|
+
'schema:template',
|
|
26
|
+
'schema:template-step',
|
|
27
|
+
'schema:status-flow',
|
|
28
|
+
'schema:status',
|
|
29
|
+
'config:kv'
|
|
30
|
+
]
|
|
31
|
+
for (const key of expectedKeys) {
|
|
32
|
+
expect(CONTENT_KIND_REGISTRY).toHaveProperty(key)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('maps schema:pipeline to the pipelineKind definition', () => {
|
|
37
|
+
expect(CONTENT_KIND_REGISTRY['schema:pipeline']).toBe(pipelineKind)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('maps schema:stage to the stageKind definition', () => {
|
|
41
|
+
expect(CONTENT_KIND_REGISTRY['schema:stage']).toBe(stageKind)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('maps schema:template to the templateKind definition', () => {
|
|
45
|
+
expect(CONTENT_KIND_REGISTRY['schema:template']).toBe(templateKind)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('maps schema:template-step to the templateStepKind definition', () => {
|
|
49
|
+
expect(CONTENT_KIND_REGISTRY['schema:template-step']).toBe(templateStepKind)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('maps schema:status-flow to the statusFlowKind definition', () => {
|
|
53
|
+
expect(CONTENT_KIND_REGISTRY['schema:status-flow']).toBe(statusFlowKind)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('maps schema:status to the statusKind definition', () => {
|
|
57
|
+
expect(CONTENT_KIND_REGISTRY['schema:status']).toBe(statusKind)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('maps config:kv to the configKvKind definition', () => {
|
|
61
|
+
expect(CONTENT_KIND_REGISTRY['config:kv']).toBe(configKvKind)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('has exactly 7 keys (no extra unintended entries)', () => {
|
|
65
|
+
expect(Object.keys(CONTENT_KIND_REGISTRY)).toHaveLength(7)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('every entry has kind, type, payloadSchema, label, and description fields', () => {
|
|
69
|
+
for (const [key, def] of Object.entries(CONTENT_KIND_REGISTRY)) {
|
|
70
|
+
expect(def).toHaveProperty('kind')
|
|
71
|
+
expect(def).toHaveProperty('type')
|
|
72
|
+
expect(def).toHaveProperty('payloadSchema')
|
|
73
|
+
expect(typeof def.kind).toBe('string')
|
|
74
|
+
expect(typeof def.type).toBe('string')
|
|
75
|
+
expect(def.label).toBeTruthy()
|
|
76
|
+
expect(def.description).toBeTruthy()
|
|
77
|
+
// key must equal kind:type
|
|
78
|
+
expect(key).toBe(`${def.kind}:${def.type}`)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// parentTypes constraint
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
describe('parentTypes constraints', () => {
|
|
88
|
+
it('schema:pipeline has empty parentTypes (top-level, no parent required)', () => {
|
|
89
|
+
expect(pipelineKind.parentTypes).toEqual([])
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('schema:stage parentTypes is ["schema:pipeline"]', () => {
|
|
93
|
+
expect(stageKind.parentTypes).toEqual(['schema:pipeline'])
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('schema:template has empty parentTypes', () => {
|
|
97
|
+
expect(templateKind.parentTypes).toEqual([])
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('schema:template-step parentTypes is ["schema:template"]', () => {
|
|
101
|
+
expect(templateStepKind.parentTypes).toEqual(['schema:template'])
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('schema:status-flow has empty parentTypes', () => {
|
|
105
|
+
expect(statusFlowKind.parentTypes).toEqual([])
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('schema:status parentTypes is ["schema:status-flow"]', () => {
|
|
109
|
+
expect(statusKind.parentTypes).toEqual(['schema:status-flow'])
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('config:kv has empty parentTypes', () => {
|
|
113
|
+
expect(configKvKind.parentTypes).toEqual([])
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// lookupContentType
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
describe('lookupContentType', () => {
|
|
122
|
+
it('returns the correct definition for schema:pipeline', () => {
|
|
123
|
+
const def = lookupContentType('schema', 'pipeline')
|
|
124
|
+
expect(def).toBe(pipelineKind)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('returns the correct definition for schema:stage', () => {
|
|
128
|
+
const def = lookupContentType('schema', 'stage')
|
|
129
|
+
expect(def).toBe(stageKind)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('returns the correct definition for config:kv', () => {
|
|
133
|
+
const def = lookupContentType('config', 'kv')
|
|
134
|
+
expect(def).toBe(configKvKind)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('returns undefined for an unregistered kind (D2: not an error)', () => {
|
|
138
|
+
const def = lookupContentType('tenant', 'custom-thing')
|
|
139
|
+
expect(def).toBeUndefined()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('returns undefined for unknown kind with valid type', () => {
|
|
143
|
+
const def = lookupContentType('unknown', 'pipeline')
|
|
144
|
+
expect(def).toBeUndefined()
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('returns undefined for valid kind with unknown type', () => {
|
|
148
|
+
const def = lookupContentType('schema', 'not-a-real-type')
|
|
149
|
+
expect(def).toBeUndefined()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('returns undefined for empty kind string', () => {
|
|
153
|
+
const def = lookupContentType('', 'pipeline')
|
|
154
|
+
expect(def).toBeUndefined()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('returns undefined for empty type string', () => {
|
|
158
|
+
const def = lookupContentType('schema', '')
|
|
159
|
+
expect(def).toBeUndefined()
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// defineContentType (identity factory, L16)
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
describe('defineContentType', () => {
|
|
168
|
+
it('is an identity function — returns the same object reference', () => {
|
|
169
|
+
const def: ContentTypeDefinition<{ name: string }> = {
|
|
170
|
+
kind: 'tenant',
|
|
171
|
+
type: 'custom',
|
|
172
|
+
payloadSchema: { safeParse: () => ({ success: true, data: { name: 'x' } }) } as unknown as ContentTypeDefinition<{
|
|
173
|
+
name: string
|
|
174
|
+
}>['payloadSchema'],
|
|
175
|
+
label: 'Custom',
|
|
176
|
+
description: 'A tenant-defined type.',
|
|
177
|
+
parentTypes: []
|
|
178
|
+
}
|
|
179
|
+
const result = defineContentType(def)
|
|
180
|
+
expect(result).toBe(def)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('does NOT mutate the input definition', () => {
|
|
184
|
+
const def: ContentTypeDefinition<unknown> = {
|
|
185
|
+
kind: 'schema',
|
|
186
|
+
type: 'pipeline',
|
|
187
|
+
payloadSchema: {
|
|
188
|
+
safeParse: () => ({ success: true, data: {} })
|
|
189
|
+
} as unknown as ContentTypeDefinition<unknown>['payloadSchema'],
|
|
190
|
+
parentTypes: []
|
|
191
|
+
}
|
|
192
|
+
const original = { ...def }
|
|
193
|
+
defineContentType(def)
|
|
194
|
+
expect(def).toEqual(original)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('does NOT register the definition globally (no side-effects on CONTENT_KIND_REGISTRY)', () => {
|
|
198
|
+
const tenantKey = 'tenant:my-new-type'
|
|
199
|
+
defineContentType({
|
|
200
|
+
kind: 'tenant',
|
|
201
|
+
type: 'my-new-type',
|
|
202
|
+
payloadSchema: {
|
|
203
|
+
safeParse: () => ({ success: true, data: {} })
|
|
204
|
+
} as unknown as ContentTypeDefinition<unknown>['payloadSchema'],
|
|
205
|
+
parentTypes: []
|
|
206
|
+
})
|
|
207
|
+
// After calling defineContentType, the registry must remain unchanged (D8: static)
|
|
208
|
+
expect(CONTENT_KIND_REGISTRY).not.toHaveProperty(tenantKey)
|
|
209
|
+
})
|
|
210
|
+
})
|