@elevasis/core 0.20.0 → 0.22.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 +524 -6
- package/dist/index.js +417 -42
- package/dist/knowledge/index.d.ts +151 -1
- package/dist/organization-model/index.d.ts +524 -6
- package/dist/organization-model/index.js +417 -42
- package/dist/test-utils/index.d.ts +270 -1
- package/dist/test-utils/index.js +407 -41
- package/package.json +5 -5
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +501 -303
- package/src/auth/multi-tenancy/permissions.ts +20 -8
- package/src/business/README.md +2 -2
- package/src/business/acquisition/api-schemas.test.ts +198 -0
- package/src/business/acquisition/api-schemas.ts +250 -9
- package/src/business/acquisition/build-templates.test.ts +28 -0
- package/src/business/acquisition/build-templates.ts +20 -8
- package/src/business/acquisition/index.ts +12 -0
- package/src/business/acquisition/types.ts +6 -1
- 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/tools/integration/server/adapters/apify/apify-adapter.test.ts +55 -0
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +107 -41
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.test.ts +48 -0
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.ts +99 -0
- package/src/execution/engine/tools/integration/server/adapters/apollo/index.ts +1 -0
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.test.ts +18 -0
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.ts +194 -0
- package/src/execution/engine/tools/integration/server/adapters/clickup/index.ts +7 -0
- package/src/execution/engine/workflow/types.ts +7 -0
- package/src/integrations/credentials/api-schemas.ts +21 -2
- package/src/integrations/credentials/schemas.ts +200 -164
- package/src/organization-model/README.md +10 -3
- package/src/organization-model/__tests__/defaults.test.ts +6 -0
- package/src/organization-model/__tests__/domains/resources.test.ts +188 -0
- package/src/organization-model/__tests__/domains/roles.test.ts +402 -347
- package/src/organization-model/__tests__/domains/systems.test.ts +193 -0
- package/src/organization-model/__tests__/knowledge.test.ts +39 -0
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +7 -4
- package/src/organization-model/__tests__/resolve.test.ts +1 -1
- package/src/organization-model/defaults.ts +24 -3
- package/src/organization-model/domains/knowledge.ts +3 -2
- package/src/organization-model/domains/prospecting.ts +182 -25
- package/src/organization-model/domains/resources.ts +88 -0
- package/src/organization-model/domains/roles.ts +93 -55
- package/src/organization-model/domains/sales.ts +24 -3
- package/src/organization-model/domains/systems.ts +46 -0
- package/src/organization-model/icons.ts +1 -0
- package/src/organization-model/index.ts +2 -0
- package/src/organization-model/organization-model.mdx +33 -14
- package/src/organization-model/published.ts +52 -1
- package/src/organization-model/schema.ts +121 -0
- package/src/organization-model/types.ts +46 -1
- package/src/platform/api/types.ts +38 -35
- package/src/platform/constants/versions.ts +1 -1
- package/src/platform/registry/__tests__/resource-registry.test.ts +2051 -2005
- package/src/platform/registry/__tests__/validation.test.ts +1343 -1086
- package/src/platform/registry/index.ts +14 -0
- package/src/platform/registry/resource-registry.ts +40 -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 +743 -513
- package/src/projects/api-schemas.ts +290 -267
- package/src/reference/_generated/contracts.md +501 -303
- package/src/reference/glossary.md +8 -3
- package/src/server.ts +2 -0
- package/src/supabase/database.types.ts +121 -0
|
@@ -1,347 +1,402 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
expect(result.success).toBe(true)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
expect(result.success).toBe(true)
|
|
75
|
-
if (result.success) {
|
|
76
|
-
expect(result.data.id).toBe('role-trim')
|
|
77
|
-
expect(result.data.title).toBe('Trimmed Title')
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
expect(result.data.
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
it('
|
|
138
|
-
|
|
139
|
-
expect(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
expect(
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
it('
|
|
208
|
-
const result = RolesDomainSchema.safeParse({
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
).toThrow()
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
expect(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
AgentRoleHolderSchema,
|
|
4
|
+
DEFAULT_ORGANIZATION_MODEL_ROLES,
|
|
5
|
+
HumanRoleHolderSchema,
|
|
6
|
+
RoleHolderSchema,
|
|
7
|
+
RoleSchema,
|
|
8
|
+
RolesDomainSchema
|
|
9
|
+
} from '../../domains/roles'
|
|
10
|
+
import { resolveOrganizationModel } from '../../resolve'
|
|
11
|
+
|
|
12
|
+
describe('RoleSchema - positive parse', () => {
|
|
13
|
+
it('accepts a fully-populated role', () => {
|
|
14
|
+
const result = RoleSchema.safeParse({
|
|
15
|
+
id: 'role-ceo',
|
|
16
|
+
title: 'CEO',
|
|
17
|
+
responsibilities: ['Set company direction', 'Hire and develop leadership team'],
|
|
18
|
+
reportsToId: undefined,
|
|
19
|
+
heldBy: { kind: 'human', userId: 'user.alice' },
|
|
20
|
+
responsibleFor: ['sys.platform']
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
expect(result.success).toBe(true)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('accepts a minimal role - only id and title required', () => {
|
|
27
|
+
const result = RoleSchema.safeParse({ id: 'role-minimal', title: 'Head of Sales' })
|
|
28
|
+
|
|
29
|
+
expect(result.success).toBe(true)
|
|
30
|
+
if (result.success) {
|
|
31
|
+
expect(result.data.id).toBe('role-minimal')
|
|
32
|
+
expect(result.data.title).toBe('Head of Sales')
|
|
33
|
+
expect(result.data.responsibilities).toEqual([])
|
|
34
|
+
expect(result.data.reportsToId).toBeUndefined()
|
|
35
|
+
expect(result.data.heldBy).toBeUndefined()
|
|
36
|
+
expect(result.data.responsibleFor).toBeUndefined()
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('accepts human, agent, team, and multi-holder variants', () => {
|
|
41
|
+
expect(
|
|
42
|
+
RoleSchema.safeParse({ id: 'role-human', title: 'Human', heldBy: { kind: 'human', userId: 'user.alice' } })
|
|
43
|
+
.success
|
|
44
|
+
).toBe(true)
|
|
45
|
+
expect(
|
|
46
|
+
RoleSchema.safeParse({
|
|
47
|
+
id: 'role-agent',
|
|
48
|
+
title: 'Agent',
|
|
49
|
+
heldBy: { kind: 'agent', agentId: 'command-center-assistant' }
|
|
50
|
+
}).success
|
|
51
|
+
).toBe(true)
|
|
52
|
+
expect(
|
|
53
|
+
RoleSchema.safeParse({ id: 'role-team', title: 'Team', heldBy: { kind: 'team', memberIds: ['user.alice'] } })
|
|
54
|
+
.success
|
|
55
|
+
).toBe(true)
|
|
56
|
+
expect(
|
|
57
|
+
RoleSchema.safeParse({
|
|
58
|
+
id: 'role-multi',
|
|
59
|
+
title: 'Multi',
|
|
60
|
+
heldBy: [
|
|
61
|
+
{ kind: 'human', userId: 'user.alice' },
|
|
62
|
+
{ kind: 'team', memberIds: ['user.bob', 'user.carol'] }
|
|
63
|
+
]
|
|
64
|
+
}).success
|
|
65
|
+
).toBe(true)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('trims whitespace from id and title', () => {
|
|
69
|
+
const result = RoleSchema.safeParse({
|
|
70
|
+
id: ' role-trim ',
|
|
71
|
+
title: ' Trimmed Title '
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
expect(result.success).toBe(true)
|
|
75
|
+
if (result.success) {
|
|
76
|
+
expect(result.data.id).toBe('role-trim')
|
|
77
|
+
expect(result.data.title).toBe('Trimmed Title')
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('RoleSchema - default values', () => {
|
|
83
|
+
it('responsibilities defaults to empty array when omitted', () => {
|
|
84
|
+
const result = RoleSchema.safeParse({ id: 'role-defaults', title: 'Engineer' })
|
|
85
|
+
|
|
86
|
+
expect(result.success).toBe(true)
|
|
87
|
+
if (result.success) {
|
|
88
|
+
expect(result.data.responsibilities).toEqual([])
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('reportsToId, heldBy, and responsibleFor are optional', () => {
|
|
93
|
+
const result = RoleSchema.safeParse({ id: 'role-defaults', title: 'Engineer' })
|
|
94
|
+
|
|
95
|
+
expect(result.success).toBe(true)
|
|
96
|
+
if (result.success) {
|
|
97
|
+
expect(result.data.reportsToId).toBeUndefined()
|
|
98
|
+
expect(result.data.heldBy).toBeUndefined()
|
|
99
|
+
expect(result.data.responsibleFor).toBeUndefined()
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('RoleSchema - negative parse', () => {
|
|
105
|
+
it('rejects missing or invalid required fields', () => {
|
|
106
|
+
expect(RoleSchema.safeParse({ title: 'No ID Role' }).success).toBe(false)
|
|
107
|
+
expect(RoleSchema.safeParse({ id: '', title: 'Empty ID' }).success).toBe(false)
|
|
108
|
+
expect(RoleSchema.safeParse({ id: ' ', title: 'Whitespace ID' }).success).toBe(false)
|
|
109
|
+
expect(RoleSchema.safeParse({ id: 'role-no-title' }).success).toBe(false)
|
|
110
|
+
expect(RoleSchema.safeParse({ id: 'role-empty-title', title: '' }).success).toBe(false)
|
|
111
|
+
expect(RoleSchema.safeParse({ id: 'role-whitespace-title', title: ' ' }).success).toBe(false)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('rejects responsibilities as a non-array value', () => {
|
|
115
|
+
const result = RoleSchema.safeParse({
|
|
116
|
+
id: 'role-bad-resp',
|
|
117
|
+
title: 'Bad Role',
|
|
118
|
+
responsibilities: 'single string'
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
expect(result.success).toBe(false)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('rejects legacy string heldBy values and invalid holders', () => {
|
|
125
|
+
expect(RoleSchema.safeParse({ id: 'role-legacy-holder', title: 'Legacy Holder', heldBy: 'Alice' }).success).toBe(
|
|
126
|
+
false
|
|
127
|
+
)
|
|
128
|
+
expect(
|
|
129
|
+
RoleSchema.safeParse({ id: 'role-empty-team', title: 'Empty Team', heldBy: { kind: 'team', memberIds: [] } })
|
|
130
|
+
.success
|
|
131
|
+
).toBe(false)
|
|
132
|
+
expect(RoleSchema.safeParse({ id: 'role-empty-holders', title: 'Empty Holders', heldBy: [] }).success).toBe(false)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('RoleHolderSchema', () => {
|
|
137
|
+
it('accepts human, agent, and team holder variants', () => {
|
|
138
|
+
expect(HumanRoleHolderSchema.safeParse({ kind: 'human', userId: 'user.alice' }).success).toBe(true)
|
|
139
|
+
expect(AgentRoleHolderSchema.safeParse({ kind: 'agent', agentId: 'command-center-assistant' }).success).toBe(true)
|
|
140
|
+
expect(RoleHolderSchema.safeParse({ kind: 'team', memberIds: ['user.alice'] }).success).toBe(true)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('rejects unknown holder kinds', () => {
|
|
144
|
+
expect(RoleHolderSchema.safeParse({ kind: 'contractor', userId: 'user.alice' }).success).toBe(false)
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe('RoleSchema - plain-language field names (no EOS jargon)', () => {
|
|
149
|
+
it('parsed role has the plain-language keys title / responsibilities / reportsToId / heldBy / responsibleFor', () => {
|
|
150
|
+
const result = RoleSchema.safeParse({
|
|
151
|
+
id: 'role-plain',
|
|
152
|
+
title: 'Product Lead',
|
|
153
|
+
responsibilities: ['Own roadmap'],
|
|
154
|
+
reportsToId: 'role-ceo',
|
|
155
|
+
heldBy: { kind: 'human', userId: 'user.dave' },
|
|
156
|
+
responsibleFor: ['sys.product']
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
expect(result.success).toBe(true)
|
|
160
|
+
if (result.success) {
|
|
161
|
+
const keys = Object.keys(result.data)
|
|
162
|
+
expect(keys).toContain('title')
|
|
163
|
+
expect(keys).toContain('responsibilities')
|
|
164
|
+
expect(keys).toContain('reportsToId')
|
|
165
|
+
expect(keys).toContain('heldBy')
|
|
166
|
+
expect(keys).toContain('responsibleFor')
|
|
167
|
+
expect(keys).not.toContain('seatTitle')
|
|
168
|
+
expect(keys).not.toContain('accountabilities')
|
|
169
|
+
expect(keys).not.toContain('seat')
|
|
170
|
+
expect(keys).not.toContain('seats')
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('RolesDomainSchema - structural', () => {
|
|
176
|
+
it('accepts an empty roles array', () => {
|
|
177
|
+
expect(RolesDomainSchema.safeParse({ roles: [] }).success).toBe(true)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('defaults roles to empty array when key is omitted', () => {
|
|
181
|
+
const result = RolesDomainSchema.safeParse({})
|
|
182
|
+
|
|
183
|
+
expect(result.success).toBe(true)
|
|
184
|
+
if (result.success) {
|
|
185
|
+
expect(result.data.roles).toEqual([])
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('accepts multiple valid roles', () => {
|
|
190
|
+
const result = RolesDomainSchema.safeParse({
|
|
191
|
+
roles: [
|
|
192
|
+
{ id: 'role-a', title: 'CEO' },
|
|
193
|
+
{ id: 'role-b', title: 'COO', reportsToId: 'role-a' }
|
|
194
|
+
]
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
expect(result.success).toBe(true)
|
|
198
|
+
if (result.success) {
|
|
199
|
+
expect(result.data.roles).toHaveLength(2)
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('rejects roles as a non-array value', () => {
|
|
204
|
+
expect(RolesDomainSchema.safeParse({ roles: 'not an array' }).success).toBe(false)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('DEFAULT_ORGANIZATION_MODEL_ROLES constant matches schema parse of empty object', () => {
|
|
208
|
+
const result = RolesDomainSchema.safeParse({})
|
|
209
|
+
|
|
210
|
+
expect(result.success).toBe(true)
|
|
211
|
+
if (result.success) {
|
|
212
|
+
expect(result.data).toEqual(DEFAULT_ORGANIZATION_MODEL_ROLES)
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('resolveOrganizationModel - roles cross-ref (reportsToId)', () => {
|
|
218
|
+
it('passes when reportsToId references a declared role id', () => {
|
|
219
|
+
expect(() =>
|
|
220
|
+
resolveOrganizationModel({
|
|
221
|
+
roles: {
|
|
222
|
+
roles: [
|
|
223
|
+
{ id: 'role-ceo', title: 'CEO' },
|
|
224
|
+
{ id: 'role-coo', title: 'COO', reportsToId: 'role-ceo' }
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
).not.toThrow()
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('throws when reportsToId references a non-existent role', () => {
|
|
232
|
+
expect(() =>
|
|
233
|
+
resolveOrganizationModel({
|
|
234
|
+
roles: {
|
|
235
|
+
roles: [{ id: 'role-orphan', title: 'Orphan Role', reportsToId: 'nonexistent-role' }]
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
).toThrow()
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('throws with a message referencing the unknown reportsToId', () => {
|
|
242
|
+
expect(() =>
|
|
243
|
+
resolveOrganizationModel({
|
|
244
|
+
roles: {
|
|
245
|
+
roles: [{ id: 'role-bad-ref', title: 'Bad Ref Role', reportsToId: 'ghost-role' }]
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
).toThrow(/ghost-role/)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('passes when reportsToId is absent or roles array is empty', () => {
|
|
252
|
+
expect(() =>
|
|
253
|
+
resolveOrganizationModel({
|
|
254
|
+
roles: {
|
|
255
|
+
roles: [{ id: 'role-top', title: 'Top Level Role' }]
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
).not.toThrow()
|
|
259
|
+
expect(() => resolveOrganizationModel({ roles: { roles: [] } })).not.toThrow()
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
describe('resolveOrganizationModel - roles cross-ref (responsibleFor and heldBy)', () => {
|
|
264
|
+
const validSystem = {
|
|
265
|
+
id: 'sys.platform',
|
|
266
|
+
title: 'Platform',
|
|
267
|
+
description: 'Owns platform operations.',
|
|
268
|
+
kind: 'platform' as const,
|
|
269
|
+
status: 'active' as const
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const validAgentResource = {
|
|
273
|
+
id: 'command-center-assistant',
|
|
274
|
+
kind: 'agent' as const,
|
|
275
|
+
systemId: 'sys.platform',
|
|
276
|
+
status: 'active' as const,
|
|
277
|
+
agentKind: 'system' as const,
|
|
278
|
+
sessionCapable: true
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
it('passes when responsibleFor references a declared system id', () => {
|
|
282
|
+
expect(() =>
|
|
283
|
+
resolveOrganizationModel({
|
|
284
|
+
systems: { systems: [validSystem] },
|
|
285
|
+
roles: {
|
|
286
|
+
roles: [{ id: 'role.platform-owner', title: 'Platform Owner', responsibleFor: ['sys.platform'] }]
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
).not.toThrow()
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('throws when responsibleFor references an unknown system', () => {
|
|
293
|
+
expect(() =>
|
|
294
|
+
resolveOrganizationModel({
|
|
295
|
+
roles: {
|
|
296
|
+
roles: [{ id: 'role.platform-owner', title: 'Platform Owner', responsibleFor: ['sys.missing'] }]
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
).toThrow(/unknown responsibleFor system/)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('passes when an agent holder references a declared agent resource', () => {
|
|
303
|
+
expect(() =>
|
|
304
|
+
resolveOrganizationModel({
|
|
305
|
+
systems: { systems: [validSystem] },
|
|
306
|
+
resources: { entries: [validAgentResource] },
|
|
307
|
+
roles: {
|
|
308
|
+
roles: [
|
|
309
|
+
{
|
|
310
|
+
id: 'role.platform-assistant',
|
|
311
|
+
title: 'Platform Assistant',
|
|
312
|
+
heldBy: { kind: 'agent', agentId: 'command-center-assistant' }
|
|
313
|
+
}
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
).not.toThrow()
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('throws when an agent holder references an unknown resource', () => {
|
|
321
|
+
expect(() =>
|
|
322
|
+
resolveOrganizationModel({
|
|
323
|
+
roles: {
|
|
324
|
+
roles: [
|
|
325
|
+
{
|
|
326
|
+
id: 'role.platform-assistant',
|
|
327
|
+
title: 'Platform Assistant',
|
|
328
|
+
heldBy: { kind: 'agent', agentId: 'missing-agent' }
|
|
329
|
+
}
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
).toThrow(/unknown agent holder resource/)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('throws when an agent holder references a non-agent resource', () => {
|
|
337
|
+
expect(() =>
|
|
338
|
+
resolveOrganizationModel({
|
|
339
|
+
systems: { systems: [validSystem] },
|
|
340
|
+
resources: {
|
|
341
|
+
entries: [
|
|
342
|
+
{
|
|
343
|
+
id: 'LGN-01-company-scrape',
|
|
344
|
+
kind: 'workflow',
|
|
345
|
+
systemId: 'sys.platform',
|
|
346
|
+
status: 'active'
|
|
347
|
+
}
|
|
348
|
+
]
|
|
349
|
+
},
|
|
350
|
+
roles: {
|
|
351
|
+
roles: [
|
|
352
|
+
{
|
|
353
|
+
id: 'role.platform-assistant',
|
|
354
|
+
title: 'Platform Assistant',
|
|
355
|
+
heldBy: { kind: 'agent', agentId: 'LGN-01-company-scrape' }
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
).toThrow(/must reference an agent resource/)
|
|
361
|
+
})
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
describe('resolveOrganizationModel - roles domain integration', () => {
|
|
365
|
+
it('merges partial roles override and preserves empty roles default', () => {
|
|
366
|
+
const model = resolveOrganizationModel({ roles: { roles: [] } })
|
|
367
|
+
expect(model.roles.roles).toEqual([])
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('omitting roles key entirely resolves to default empty roles', () => {
|
|
371
|
+
const model = resolveOrganizationModel({})
|
|
372
|
+
expect(model.roles).toBeDefined()
|
|
373
|
+
expect(model.roles.roles).toEqual([])
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('does not bleed roles changes into other top-level domains', () => {
|
|
377
|
+
const model = resolveOrganizationModel({
|
|
378
|
+
roles: {
|
|
379
|
+
roles: [{ id: 'role-ceo', title: 'CEO' }]
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
expect(model.identity).toBeDefined()
|
|
384
|
+
expect(model.customers).toBeDefined()
|
|
385
|
+
expect(model.offerings).toBeDefined()
|
|
386
|
+
expect(model.features).toBeDefined()
|
|
387
|
+
expect(model.navigation).toBeDefined()
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('resolved model includes roles domain alongside other new domains', () => {
|
|
391
|
+
const model = resolveOrganizationModel({
|
|
392
|
+
customers: { segments: [{ id: 'seg-a', name: 'Segment A' }] },
|
|
393
|
+
roles: {
|
|
394
|
+
roles: [{ id: 'role-ceo', title: 'CEO', heldBy: { kind: 'human', userId: 'user.alice' } }]
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
expect(model.customers.segments).toHaveLength(1)
|
|
399
|
+
expect(model.roles.roles).toHaveLength(1)
|
|
400
|
+
expect(model.roles.roles[0].heldBy).toEqual({ kind: 'human', userId: 'user.alice' })
|
|
401
|
+
})
|
|
402
|
+
})
|