@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
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recursive-system-schema.test.ts
|
|
3
|
+
*
|
|
4
|
+
* Tests for B3, B4, B5, L19, and D2 content-node refines in OrganizationModelSchema.
|
|
5
|
+
* Each constraint has ≥1 positive (passes) and ≥1 negative (error) assertion.
|
|
6
|
+
*
|
|
7
|
+
* B3 — Cycle detection: parentContentId chain must not form a cycle.
|
|
8
|
+
* B4 — Same-system-only: parentContentId must resolve within the same content map.
|
|
9
|
+
* B5 — Payload validation: registered (kind, type) pairs validate data.
|
|
10
|
+
* L19 — Same-meta-kind parent constraint.
|
|
11
|
+
* D2 — Unregistered (kind, type) passes through without error.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, expect, it } from 'vitest'
|
|
14
|
+
import { resolveOrganizationModel } from '../resolve'
|
|
15
|
+
import { OrganizationModelSchema } from '../schema'
|
|
16
|
+
import { DEFAULT_ORGANIZATION_MODEL_BRANDING } from '../domains/branding'
|
|
17
|
+
|
|
18
|
+
// Phase 4 (D8): sales, prospecting, projects top-level OM fields removed.
|
|
19
|
+
// DEFAULT_ORGANIZATION_MODEL_SALES / _PROSPECTING / _PROJECTS no longer exported.
|
|
20
|
+
// makeMinimalModel no longer includes those fields — they were extra props that
|
|
21
|
+
// the schema stripped anyway. Removing them has no effect on the test fixtures.
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Minimal model factory (shared with schema.test.ts convention)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function makeSystem(id: string, path = `/${id.replaceAll('.', '/')}`) {
|
|
28
|
+
return {
|
|
29
|
+
id,
|
|
30
|
+
order: 10,
|
|
31
|
+
label: id,
|
|
32
|
+
enabled: true,
|
|
33
|
+
lifecycle: 'active' as const,
|
|
34
|
+
path
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeMinimalModel(systems: Record<string, unknown> = {}, extra: Record<string, unknown> = {}) {
|
|
39
|
+
const entityOwnerId = Object.keys(systems)[0] ?? 'dashboard'
|
|
40
|
+
return {
|
|
41
|
+
version: 1 as const,
|
|
42
|
+
branding: DEFAULT_ORGANIZATION_MODEL_BRANDING,
|
|
43
|
+
entities: {
|
|
44
|
+
'crm.deal': { id: 'crm.deal', order: 10, label: 'Deal', ownedBySystemId: entityOwnerId },
|
|
45
|
+
'leadgen.list': { id: 'leadgen.list', order: 20, label: 'Lead List', ownedBySystemId: entityOwnerId },
|
|
46
|
+
'leadgen.company': { id: 'leadgen.company', order: 30, label: 'Lead Company', ownedBySystemId: entityOwnerId },
|
|
47
|
+
'leadgen.contact': { id: 'leadgen.contact', order: 40, label: 'Lead Contact', ownedBySystemId: entityOwnerId },
|
|
48
|
+
'delivery.project': { id: 'delivery.project', order: 50, label: 'Project', ownedBySystemId: entityOwnerId },
|
|
49
|
+
'delivery.milestone': {
|
|
50
|
+
id: 'delivery.milestone',
|
|
51
|
+
order: 60,
|
|
52
|
+
label: 'Milestone',
|
|
53
|
+
ownedBySystemId: entityOwnerId
|
|
54
|
+
},
|
|
55
|
+
'delivery.task': { id: 'delivery.task', order: 70, label: 'Task', ownedBySystemId: entityOwnerId }
|
|
56
|
+
},
|
|
57
|
+
systems,
|
|
58
|
+
...extra
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getIssueMessages(data: unknown): string[] {
|
|
63
|
+
const result = OrganizationModelSchema.safeParse(data)
|
|
64
|
+
if (result.success) return []
|
|
65
|
+
return result.error.issues.map((issue) => issue.message)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function parseSucceeds(data: unknown): boolean {
|
|
69
|
+
return OrganizationModelSchema.safeParse(data).success
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// 2-level subsystem + content fixture round-trips
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
describe('2-level subsystems + content (positive, structural)', () => {
|
|
77
|
+
it('parses a model with a top-level system that has a subsystem via resolveOrganizationModel', () => {
|
|
78
|
+
const model = resolveOrganizationModel({
|
|
79
|
+
systems: {
|
|
80
|
+
ops: { id: 'ops', order: 99, label: 'Ops', enabled: true, path: '/ops' },
|
|
81
|
+
'ops.sub': {
|
|
82
|
+
id: 'ops.sub',
|
|
83
|
+
order: 100,
|
|
84
|
+
label: 'Sub',
|
|
85
|
+
enabled: true,
|
|
86
|
+
path: '/ops/sub',
|
|
87
|
+
parentSystemId: 'ops'
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
expect(model.systems['ops']).toBeDefined()
|
|
92
|
+
expect(model.systems['ops.sub']).toBeDefined()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('parses a system with a content map carrying a valid registered node', () => {
|
|
96
|
+
const data = makeMinimalModel({
|
|
97
|
+
hub: {
|
|
98
|
+
...makeSystem('hub', '/hub'),
|
|
99
|
+
content: {
|
|
100
|
+
'main-pipeline': {
|
|
101
|
+
kind: 'schema',
|
|
102
|
+
type: 'pipeline',
|
|
103
|
+
label: 'Main Pipeline',
|
|
104
|
+
data: { entityId: 'crm.deal' }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
expect(parseSucceeds(data)).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('parses a 2-level subsystem hierarchy with content at each level', () => {
|
|
113
|
+
const data = makeMinimalModel({
|
|
114
|
+
parent: {
|
|
115
|
+
...makeSystem('parent', '/parent'),
|
|
116
|
+
content: {
|
|
117
|
+
'parent-config': {
|
|
118
|
+
kind: 'config',
|
|
119
|
+
type: 'kv',
|
|
120
|
+
label: 'Parent Config',
|
|
121
|
+
data: { entries: { maxBatch: 10 } }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
'parent.child': {
|
|
126
|
+
...makeSystem('parent.child', '/parent/child'),
|
|
127
|
+
parentSystemId: 'parent',
|
|
128
|
+
content: {
|
|
129
|
+
'child-config': {
|
|
130
|
+
kind: 'config',
|
|
131
|
+
type: 'kv',
|
|
132
|
+
label: 'Child Config',
|
|
133
|
+
data: { entries: { timeout: 5000 } }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
expect(parseSucceeds(data)).toBe(true)
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// B3 — parentContentId cycle detection
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
describe('B3 — parentContentId cycle detection', () => {
|
|
147
|
+
it('POSITIVE: linear parentContentId chain (A → B, B has no parent) parses', () => {
|
|
148
|
+
const data = makeMinimalModel({
|
|
149
|
+
hub: {
|
|
150
|
+
...makeSystem('hub', '/hub'),
|
|
151
|
+
content: {
|
|
152
|
+
flow: {
|
|
153
|
+
kind: 'schema',
|
|
154
|
+
type: 'status-flow',
|
|
155
|
+
label: 'Flow',
|
|
156
|
+
data: { appliesTo: 'project' }
|
|
157
|
+
},
|
|
158
|
+
status: {
|
|
159
|
+
kind: 'schema',
|
|
160
|
+
type: 'status',
|
|
161
|
+
label: 'Active',
|
|
162
|
+
parentContentId: 'flow',
|
|
163
|
+
data: { semanticClass: 'active' }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
expect(parseSucceeds(data)).toBe(true)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('NEGATIVE: direct self-cycle (node points to itself) produces cycle error', () => {
|
|
172
|
+
const data = makeMinimalModel({
|
|
173
|
+
hub: {
|
|
174
|
+
...makeSystem('hub', '/hub'),
|
|
175
|
+
content: {
|
|
176
|
+
'self-ref': {
|
|
177
|
+
kind: 'schema',
|
|
178
|
+
type: 'pipeline',
|
|
179
|
+
label: 'Self Ref',
|
|
180
|
+
parentContentId: 'self-ref'
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
const messages = getIssueMessages(data)
|
|
186
|
+
expect(messages.some((m) => m.includes('parentContentId cycle'))).toBe(true)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('NEGATIVE: indirect 2-node cycle (A → B → A) produces cycle error', () => {
|
|
190
|
+
const data = makeMinimalModel({
|
|
191
|
+
hub: {
|
|
192
|
+
...makeSystem('hub', '/hub'),
|
|
193
|
+
content: {
|
|
194
|
+
alpha: {
|
|
195
|
+
kind: 'schema',
|
|
196
|
+
type: 'pipeline',
|
|
197
|
+
label: 'Alpha',
|
|
198
|
+
parentContentId: 'beta'
|
|
199
|
+
},
|
|
200
|
+
beta: {
|
|
201
|
+
kind: 'schema',
|
|
202
|
+
type: 'stage',
|
|
203
|
+
label: 'Beta',
|
|
204
|
+
parentContentId: 'alpha'
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
const messages = getIssueMessages(data)
|
|
210
|
+
expect(messages.some((m) => m.includes('parentContentId cycle'))).toBe(true)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// B4 — parentContentId must resolve within the same system content map
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
describe('B4 — parentContentId cross-system reference error', () => {
|
|
219
|
+
it('POSITIVE: parentContentId referencing a sibling in the same content map parses', () => {
|
|
220
|
+
const data = makeMinimalModel({
|
|
221
|
+
hub: {
|
|
222
|
+
...makeSystem('hub', '/hub'),
|
|
223
|
+
content: {
|
|
224
|
+
parent: {
|
|
225
|
+
kind: 'schema',
|
|
226
|
+
type: 'status-flow',
|
|
227
|
+
label: 'Status Flow',
|
|
228
|
+
data: { appliesTo: 'task' }
|
|
229
|
+
},
|
|
230
|
+
child: {
|
|
231
|
+
kind: 'schema',
|
|
232
|
+
type: 'status',
|
|
233
|
+
label: 'Open',
|
|
234
|
+
parentContentId: 'parent',
|
|
235
|
+
data: { semanticClass: 'active' }
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
expect(parseSucceeds(data)).toBe(true)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('NEGATIVE: parentContentId referencing a non-existent localId produces B4 error', () => {
|
|
244
|
+
const data = makeMinimalModel({
|
|
245
|
+
hub: {
|
|
246
|
+
...makeSystem('hub', '/hub'),
|
|
247
|
+
content: {
|
|
248
|
+
orphan: {
|
|
249
|
+
kind: 'schema',
|
|
250
|
+
type: 'stage',
|
|
251
|
+
label: 'Orphan',
|
|
252
|
+
parentContentId: 'does-not-exist-in-this-system'
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
const messages = getIssueMessages(data)
|
|
258
|
+
expect(
|
|
259
|
+
messages.some((m) => m.includes('does not resolve within the same system') || m.includes('parentContentId'))
|
|
260
|
+
).toBe(true)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('NEGATIVE: cross-system localId reference (sibling system content is a different map)', () => {
|
|
264
|
+
// "other-system" has "shared-node" but "hub" does NOT — so parentContentId is cross-system
|
|
265
|
+
const data = makeMinimalModel({
|
|
266
|
+
hub: {
|
|
267
|
+
...makeSystem('hub', '/hub'),
|
|
268
|
+
content: {
|
|
269
|
+
child: {
|
|
270
|
+
kind: 'schema',
|
|
271
|
+
type: 'stage',
|
|
272
|
+
label: 'Child',
|
|
273
|
+
parentContentId: 'shared-node' // exists in other-system, not hub
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
other: {
|
|
278
|
+
...makeSystem('other', '/other'),
|
|
279
|
+
content: {
|
|
280
|
+
'shared-node': {
|
|
281
|
+
kind: 'schema',
|
|
282
|
+
type: 'pipeline',
|
|
283
|
+
label: 'Shared Node'
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
const messages = getIssueMessages(data)
|
|
289
|
+
expect(messages.some((m) => m.includes('parentContentId'))).toBe(true)
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// B5 — Payload validation for registered (kind, type) pairs
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
describe('B5 — payload validation for registered kinds', () => {
|
|
298
|
+
it('POSITIVE: valid schema:pipeline data passes payload validation', () => {
|
|
299
|
+
const data = makeMinimalModel({
|
|
300
|
+
hub: {
|
|
301
|
+
...makeSystem('hub', '/hub'),
|
|
302
|
+
content: {
|
|
303
|
+
pipe: {
|
|
304
|
+
kind: 'schema',
|
|
305
|
+
type: 'pipeline',
|
|
306
|
+
label: 'Pipeline',
|
|
307
|
+
data: { entityId: 'crm.deal' }
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
expect(parseSucceeds(data)).toBe(true)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('NEGATIVE: invalid schema:pipeline data (missing required entityId) produces payload error', () => {
|
|
316
|
+
const data = makeMinimalModel({
|
|
317
|
+
hub: {
|
|
318
|
+
...makeSystem('hub', '/hub'),
|
|
319
|
+
content: {
|
|
320
|
+
pipe: {
|
|
321
|
+
kind: 'schema',
|
|
322
|
+
type: 'pipeline',
|
|
323
|
+
label: 'Pipeline',
|
|
324
|
+
data: { kanbanColor: 'blue' } // entityId is required
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
})
|
|
329
|
+
const messages = getIssueMessages(data)
|
|
330
|
+
expect(messages.some((m) => m.includes('data failed payload validation') || m.includes('payload'))).toBe(true)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('POSITIVE: valid config:kv data passes payload validation', () => {
|
|
334
|
+
const data = makeMinimalModel({
|
|
335
|
+
hub: {
|
|
336
|
+
...makeSystem('hub', '/hub'),
|
|
337
|
+
content: {
|
|
338
|
+
cfg: {
|
|
339
|
+
kind: 'config',
|
|
340
|
+
type: 'kv',
|
|
341
|
+
label: 'Config',
|
|
342
|
+
data: { entries: { flag: true, limit: 100 } }
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
expect(parseSucceeds(data)).toBe(true)
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('NEGATIVE: invalid config:kv data (entries contains an object value) produces payload error', () => {
|
|
351
|
+
const data = makeMinimalModel({
|
|
352
|
+
hub: {
|
|
353
|
+
...makeSystem('hub', '/hub'),
|
|
354
|
+
content: {
|
|
355
|
+
cfg: {
|
|
356
|
+
kind: 'config',
|
|
357
|
+
type: 'kv',
|
|
358
|
+
label: 'Config',
|
|
359
|
+
data: { entries: { nested: { key: 'value' } } } // object not allowed, only primitives
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
const messages = getIssueMessages(data)
|
|
365
|
+
expect(messages.some((m) => m.includes('payload') || m.includes('data'))).toBe(true)
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it('POSITIVE: valid schema:status-flow data passes', () => {
|
|
369
|
+
const data = makeMinimalModel({
|
|
370
|
+
hub: {
|
|
371
|
+
...makeSystem('hub', '/hub'),
|
|
372
|
+
content: {
|
|
373
|
+
flow: {
|
|
374
|
+
kind: 'schema',
|
|
375
|
+
type: 'status-flow',
|
|
376
|
+
label: 'Flow',
|
|
377
|
+
data: { appliesTo: 'project' }
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
expect(parseSucceeds(data)).toBe(true)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it('NEGATIVE: invalid schema:status-flow data (bad appliesTo value)', () => {
|
|
386
|
+
const data = makeMinimalModel({
|
|
387
|
+
hub: {
|
|
388
|
+
...makeSystem('hub', '/hub'),
|
|
389
|
+
content: {
|
|
390
|
+
flow: {
|
|
391
|
+
kind: 'schema',
|
|
392
|
+
type: 'status-flow',
|
|
393
|
+
label: 'Flow',
|
|
394
|
+
data: { appliesTo: 'organization' } // not in enum
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
})
|
|
399
|
+
const messages = getIssueMessages(data)
|
|
400
|
+
expect(messages.some((m) => m.includes('payload') || m.includes('data'))).toBe(true)
|
|
401
|
+
})
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
// D2 — Unregistered (kind, type) passes through without error
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
|
|
408
|
+
describe('D2 — unregistered (kind, type) passes through (no error)', () => {
|
|
409
|
+
it('unregistered tenant kind passes schema validation without error', () => {
|
|
410
|
+
const data = makeMinimalModel({
|
|
411
|
+
hub: {
|
|
412
|
+
...makeSystem('hub', '/hub'),
|
|
413
|
+
content: {
|
|
414
|
+
custom: {
|
|
415
|
+
kind: 'tenant',
|
|
416
|
+
type: 'custom-thing',
|
|
417
|
+
label: 'Custom Thing',
|
|
418
|
+
data: { anything: 'goes', nested: { ok: true } }
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
})
|
|
423
|
+
expect(parseSucceeds(data)).toBe(true)
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
it('unregistered kind with invalid-looking data still passes through (no payload schema to validate against)', () => {
|
|
427
|
+
const data = makeMinimalModel({
|
|
428
|
+
hub: {
|
|
429
|
+
...makeSystem('hub', '/hub'),
|
|
430
|
+
content: {
|
|
431
|
+
unknown: {
|
|
432
|
+
kind: 'experimental',
|
|
433
|
+
type: 'prototype',
|
|
434
|
+
label: 'Prototype',
|
|
435
|
+
data: { malformed: [1, 2, 3], extra: null }
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
expect(parseSucceeds(data)).toBe(true)
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('unregistered kind with parentContentId that resolves in same system also passes', () => {
|
|
444
|
+
const data = makeMinimalModel({
|
|
445
|
+
hub: {
|
|
446
|
+
...makeSystem('hub', '/hub'),
|
|
447
|
+
content: {
|
|
448
|
+
parent: {
|
|
449
|
+
kind: 'tenant',
|
|
450
|
+
type: 'group',
|
|
451
|
+
label: 'Parent Group'
|
|
452
|
+
},
|
|
453
|
+
child: {
|
|
454
|
+
kind: 'tenant',
|
|
455
|
+
type: 'item',
|
|
456
|
+
label: 'Child Item',
|
|
457
|
+
parentContentId: 'parent'
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
})
|
|
462
|
+
expect(parseSucceeds(data)).toBe(true)
|
|
463
|
+
})
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
// ---------------------------------------------------------------------------
|
|
467
|
+
// L19 — Same-meta-kind parent constraint
|
|
468
|
+
// ---------------------------------------------------------------------------
|
|
469
|
+
|
|
470
|
+
describe('L19 — same-meta-kind parent constraint', () => {
|
|
471
|
+
it('POSITIVE: schema:stage under schema:pipeline shares same kind (schema)', () => {
|
|
472
|
+
const data = makeMinimalModel({
|
|
473
|
+
hub: {
|
|
474
|
+
...makeSystem('hub', '/hub'),
|
|
475
|
+
content: {
|
|
476
|
+
pipe: {
|
|
477
|
+
kind: 'schema',
|
|
478
|
+
type: 'pipeline',
|
|
479
|
+
label: 'Pipeline',
|
|
480
|
+
data: { entityId: 'crm.deal' }
|
|
481
|
+
},
|
|
482
|
+
stage: {
|
|
483
|
+
kind: 'schema',
|
|
484
|
+
type: 'stage',
|
|
485
|
+
label: 'Stage',
|
|
486
|
+
parentContentId: 'pipe',
|
|
487
|
+
data: { semanticClass: 'open' }
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
})
|
|
492
|
+
expect(parseSucceeds(data)).toBe(true)
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
it('NEGATIVE: registered child under registered parent with different meta-kind produces L19 error', () => {
|
|
496
|
+
// schema:stage (meta-kind: schema) trying to parent under config:kv (meta-kind: config)
|
|
497
|
+
const data = makeMinimalModel({
|
|
498
|
+
hub: {
|
|
499
|
+
...makeSystem('hub', '/hub'),
|
|
500
|
+
content: {
|
|
501
|
+
'my-config': {
|
|
502
|
+
kind: 'config',
|
|
503
|
+
type: 'kv',
|
|
504
|
+
label: 'Config',
|
|
505
|
+
data: { entries: { enabled: true } }
|
|
506
|
+
},
|
|
507
|
+
'my-stage': {
|
|
508
|
+
kind: 'schema',
|
|
509
|
+
type: 'stage',
|
|
510
|
+
label: 'Stage',
|
|
511
|
+
parentContentId: 'my-config',
|
|
512
|
+
data: { semanticClass: 'open' }
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
const messages = getIssueMessages(data)
|
|
518
|
+
expect(messages.some((m) => m.includes('same-meta-kind') || m.includes('parentContentId'))).toBe(true)
|
|
519
|
+
})
|
|
520
|
+
})
|