@elevasis/core 0.5.0 → 0.6.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 (59) hide show
  1. package/dist/index.d.ts +427 -42
  2. package/dist/index.js +589 -47
  3. package/dist/organization-model/index.d.ts +427 -42
  4. package/dist/organization-model/index.js +589 -47
  5. package/package.json +3 -3
  6. package/src/__tests__/template-foundations-compatibility.test.ts +2 -2
  7. package/src/business/acquisition/types.ts +2 -0
  8. package/src/commands/queue/types/task.ts +3 -3
  9. package/src/execution/engine/index.ts +8 -0
  10. package/src/execution/engine/tools/registry.ts +26 -24
  11. package/src/execution/engine/tools/tool-maps.ts +13 -9
  12. package/src/execution/engine/workflow/types.ts +2 -3
  13. package/src/organization-model/README.md +16 -12
  14. package/src/organization-model/__tests__/defaults.test.ts +175 -0
  15. package/src/organization-model/__tests__/domains/customers.test.ts +295 -0
  16. package/src/organization-model/__tests__/domains/goals.test.ts +479 -0
  17. package/src/organization-model/__tests__/domains/identity.test.ts +278 -0
  18. package/src/organization-model/__tests__/domains/navigation.test.ts +212 -0
  19. package/src/organization-model/__tests__/domains/offerings.test.ts +419 -0
  20. package/src/organization-model/__tests__/domains/operations.test.ts +203 -0
  21. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +362 -0
  22. package/src/organization-model/__tests__/domains/roles.test.ts +347 -0
  23. package/src/organization-model/__tests__/domains/statuses.test.ts +243 -0
  24. package/src/organization-model/__tests__/foundation.test.ts +3 -3
  25. package/src/organization-model/__tests__/resolve.test.ts +447 -3
  26. package/src/organization-model/__tests__/schema.test.ts +407 -0
  27. package/src/organization-model/contracts.ts +5 -5
  28. package/src/organization-model/defaults.ts +39 -16
  29. package/src/organization-model/domains/customers.ts +75 -0
  30. package/src/organization-model/domains/goals.ts +80 -0
  31. package/src/organization-model/domains/identity.ts +87 -0
  32. package/src/organization-model/domains/navigation.ts +43 -4
  33. package/src/organization-model/domains/offerings.ts +66 -0
  34. package/src/organization-model/domains/operations.ts +85 -0
  35. package/src/organization-model/domains/{delivery.ts → projects.ts} +6 -6
  36. package/src/organization-model/domains/{lead-gen.ts → prospecting.ts} +5 -5
  37. package/src/organization-model/domains/roles.ts +55 -0
  38. package/src/organization-model/domains/sales.ts +94 -0
  39. package/src/organization-model/domains/shared.ts +30 -1
  40. package/src/organization-model/domains/statuses.ts +130 -0
  41. package/src/organization-model/index.ts +3 -3
  42. package/src/organization-model/organization-graph.mdx +1 -0
  43. package/src/organization-model/organization-model.mdx +84 -19
  44. package/src/organization-model/published.ts +53 -8
  45. package/src/organization-model/schema.ts +67 -7
  46. package/src/organization-model/types.ts +31 -7
  47. package/src/platform/constants/versions.ts +1 -1
  48. package/src/platform/registry/types.ts +1 -1
  49. package/src/projects/api-schemas.ts +1 -0
  50. package/src/reference/_generated/contracts.md +116 -8
  51. package/src/reference/glossary.md +25 -4
  52. package/src/requests/__tests__/api-schemas.test.ts +277 -0
  53. package/src/requests/api-schemas.ts +83 -0
  54. package/src/requests/index.ts +1 -0
  55. package/src/supabase/database.types.ts +88 -0
  56. package/src/organization-model/domains/crm.ts +0 -46
  57. /package/src/business/{delivery → projects}/index.ts +0 -0
  58. /package/src/business/{delivery → projects}/types.ts +0 -0
  59. /package/src/business/{crm → sales}/api-schemas.ts +0 -0
@@ -0,0 +1,295 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ CustomersDomainSchema,
4
+ CustomerSegmentSchema,
5
+ DEFAULT_ORGANIZATION_MODEL_CUSTOMERS,
6
+ FirmographicsSchema
7
+ } from '../../domains/customers'
8
+ import { resolveOrganizationModel } from '../../resolve'
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Group 1: CustomerSegmentSchema — positive parse
12
+ // ---------------------------------------------------------------------------
13
+
14
+ describe('CustomerSegmentSchema — positive parse', () => {
15
+ it('accepts a fully-populated segment', () => {
16
+ const result = CustomerSegmentSchema.safeParse({
17
+ id: 'segment-smb-agencies',
18
+ name: 'SMB Marketing Agencies',
19
+ description: 'Small marketing agencies with 1–50 employees seeking automation.',
20
+ jobsToBeDone: 'Automate client reporting and lead generation workflows.',
21
+ pains: ['Too many manual tasks', 'Clients expect faster turnaround'],
22
+ gains: ['More time for creative work', 'Higher client retention'],
23
+ firmographics: {
24
+ industry: 'Marketing Agency',
25
+ companySize: '1–50',
26
+ region: 'North America'
27
+ },
28
+ valueProp: 'Elevasis reduces manual ops by 80% for agencies under 50 people.'
29
+ })
30
+ expect(result.success).toBe(true)
31
+ })
32
+
33
+ it('accepts a minimal segment — only id required', () => {
34
+ const result = CustomerSegmentSchema.safeParse({ id: 'seg-minimal' })
35
+ expect(result.success).toBe(true)
36
+ if (result.success) {
37
+ expect(result.data.id).toBe('seg-minimal')
38
+ expect(result.data.name).toBe('')
39
+ expect(result.data.description).toBe('')
40
+ expect(result.data.jobsToBeDone).toBe('')
41
+ expect(result.data.pains).toEqual([])
42
+ expect(result.data.gains).toEqual([])
43
+ expect(result.data.firmographics).toEqual({})
44
+ expect(result.data.valueProp).toBe('')
45
+ }
46
+ })
47
+
48
+ it('trims whitespace from string fields', () => {
49
+ const result = CustomerSegmentSchema.safeParse({
50
+ id: ' seg-trim ',
51
+ name: ' Trimmed Name ',
52
+ valueProp: ' Trimmed value prop. '
53
+ })
54
+ expect(result.success).toBe(true)
55
+ if (result.success) {
56
+ expect(result.data.id).toBe('seg-trim')
57
+ expect(result.data.name).toBe('Trimmed Name')
58
+ expect(result.data.valueProp).toBe('Trimmed value prop.')
59
+ }
60
+ })
61
+
62
+ it('accepts pains and gains as non-empty arrays', () => {
63
+ const result = CustomerSegmentSchema.safeParse({
64
+ id: 'seg-arrays',
65
+ pains: ['Pain A', 'Pain B', 'Pain C'],
66
+ gains: ['Gain X', 'Gain Y']
67
+ })
68
+ expect(result.success).toBe(true)
69
+ if (result.success) {
70
+ expect(result.data.pains).toHaveLength(3)
71
+ expect(result.data.gains).toHaveLength(2)
72
+ }
73
+ })
74
+ })
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // Group 2: CustomerSegmentSchema — default values
78
+ // ---------------------------------------------------------------------------
79
+
80
+ describe('CustomerSegmentSchema — default values', () => {
81
+ it('pains defaults to empty array', () => {
82
+ const result = CustomerSegmentSchema.safeParse({ id: 'seg-defaults' })
83
+ expect(result.success).toBe(true)
84
+ if (result.success) {
85
+ expect(result.data.pains).toEqual([])
86
+ }
87
+ })
88
+
89
+ it('gains defaults to empty array', () => {
90
+ const result = CustomerSegmentSchema.safeParse({ id: 'seg-defaults' })
91
+ expect(result.success).toBe(true)
92
+ if (result.success) {
93
+ expect(result.data.gains).toEqual([])
94
+ }
95
+ })
96
+
97
+ it('firmographics defaults to empty object', () => {
98
+ const result = CustomerSegmentSchema.safeParse({ id: 'seg-defaults' })
99
+ expect(result.success).toBe(true)
100
+ if (result.success) {
101
+ expect(result.data.firmographics).toEqual({})
102
+ }
103
+ })
104
+
105
+ it('all string fields default to empty string', () => {
106
+ const result = CustomerSegmentSchema.safeParse({ id: 'seg-str-defaults' })
107
+ expect(result.success).toBe(true)
108
+ if (result.success) {
109
+ expect(result.data.name).toBe('')
110
+ expect(result.data.description).toBe('')
111
+ expect(result.data.jobsToBeDone).toBe('')
112
+ expect(result.data.valueProp).toBe('')
113
+ }
114
+ })
115
+ })
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Group 3: CustomerSegmentSchema — negative parse (wrong types / constraints)
119
+ // ---------------------------------------------------------------------------
120
+
121
+ describe('CustomerSegmentSchema — negative parse', () => {
122
+ it('rejects missing id', () => {
123
+ const result = CustomerSegmentSchema.safeParse({ name: 'No ID Segment' })
124
+ expect(result.success).toBe(false)
125
+ })
126
+
127
+ it('rejects empty string id', () => {
128
+ const result = CustomerSegmentSchema.safeParse({ id: '' })
129
+ expect(result.success).toBe(false)
130
+ })
131
+
132
+ it('rejects whitespace-only id (trims to empty string)', () => {
133
+ const result = CustomerSegmentSchema.safeParse({ id: ' ' })
134
+ expect(result.success).toBe(false)
135
+ })
136
+
137
+ it('rejects id as a number', () => {
138
+ const result = CustomerSegmentSchema.safeParse({ id: 42 })
139
+ expect(result.success).toBe(false)
140
+ })
141
+
142
+ it('rejects pains as a non-array value', () => {
143
+ const result = CustomerSegmentSchema.safeParse({ id: 'seg-bad-pains', pains: 'not an array' })
144
+ expect(result.success).toBe(false)
145
+ })
146
+
147
+ it('rejects gains containing a non-string element', () => {
148
+ const result = CustomerSegmentSchema.safeParse({ id: 'seg-bad-gains', gains: [42, 'valid'] })
149
+ expect(result.success).toBe(false)
150
+ })
151
+
152
+ it('rejects name exceeding 200 characters', () => {
153
+ const result = CustomerSegmentSchema.safeParse({ id: 'seg-long-name', name: 'x'.repeat(201) })
154
+ expect(result.success).toBe(false)
155
+ })
156
+
157
+ it('rejects description exceeding 2000 characters', () => {
158
+ const result = CustomerSegmentSchema.safeParse({ id: 'seg-long-desc', description: 'x'.repeat(2001) })
159
+ expect(result.success).toBe(false)
160
+ })
161
+ })
162
+
163
+ // ---------------------------------------------------------------------------
164
+ // Group 4: FirmographicsSchema
165
+ // ---------------------------------------------------------------------------
166
+
167
+ describe('FirmographicsSchema', () => {
168
+ it('accepts a fully-populated firmographics object', () => {
169
+ const result = FirmographicsSchema.safeParse({
170
+ industry: 'Legal',
171
+ companySize: '11–50',
172
+ region: 'Europe'
173
+ })
174
+ expect(result.success).toBe(true)
175
+ })
176
+
177
+ it('accepts an empty object (all fields optional)', () => {
178
+ const result = FirmographicsSchema.safeParse({})
179
+ expect(result.success).toBe(true)
180
+ if (result.success) {
181
+ expect(result.data.industry).toBeUndefined()
182
+ expect(result.data.companySize).toBeUndefined()
183
+ expect(result.data.region).toBeUndefined()
184
+ }
185
+ })
186
+
187
+ it('accepts partial firmographics — only industry provided', () => {
188
+ const result = FirmographicsSchema.safeParse({ industry: 'SaaS' })
189
+ expect(result.success).toBe(true)
190
+ if (result.success) {
191
+ expect(result.data.industry).toBe('SaaS')
192
+ expect(result.data.companySize).toBeUndefined()
193
+ expect(result.data.region).toBeUndefined()
194
+ }
195
+ })
196
+
197
+ it('rejects industry as a number', () => {
198
+ const result = FirmographicsSchema.safeParse({ industry: 99 })
199
+ expect(result.success).toBe(false)
200
+ })
201
+ })
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // Group 5: CustomersDomainSchema
205
+ // ---------------------------------------------------------------------------
206
+
207
+ describe('CustomersDomainSchema — structural', () => {
208
+ it('accepts an empty segments array', () => {
209
+ const result = CustomersDomainSchema.safeParse({ segments: [] })
210
+ expect(result.success).toBe(true)
211
+ })
212
+
213
+ it('defaults segments to empty array when key is omitted', () => {
214
+ const result = CustomersDomainSchema.safeParse({})
215
+ expect(result.success).toBe(true)
216
+ if (result.success) {
217
+ expect(result.data.segments).toEqual([])
218
+ }
219
+ })
220
+
221
+ it('accepts multiple valid segments', () => {
222
+ const result = CustomersDomainSchema.safeParse({
223
+ segments: [
224
+ { id: 'seg-a', name: 'Segment A' },
225
+ { id: 'seg-b', name: 'Segment B', pains: ['Pain 1'] }
226
+ ]
227
+ })
228
+ expect(result.success).toBe(true)
229
+ if (result.success) {
230
+ expect(result.data.segments).toHaveLength(2)
231
+ }
232
+ })
233
+
234
+ it('rejects segments as a non-array value', () => {
235
+ const result = CustomersDomainSchema.safeParse({ segments: 'not an array' })
236
+ expect(result.success).toBe(false)
237
+ })
238
+
239
+ it('DEFAULT_ORGANIZATION_MODEL_CUSTOMERS constant matches schema parse of empty object', () => {
240
+ const result = CustomersDomainSchema.safeParse({})
241
+ expect(result.success).toBe(true)
242
+ if (result.success) {
243
+ expect(result.data).toEqual(DEFAULT_ORGANIZATION_MODEL_CUSTOMERS)
244
+ }
245
+ })
246
+ })
247
+
248
+ // ---------------------------------------------------------------------------
249
+ // Group 6: Integration — resolveOrganizationModel
250
+ // ---------------------------------------------------------------------------
251
+
252
+ describe('resolveOrganizationModel — customers domain integration', () => {
253
+ it('merges partial customers override and preserves empty segments default', () => {
254
+ const model = resolveOrganizationModel({
255
+ customers: { segments: [] }
256
+ })
257
+ expect(model.customers.segments).toEqual([])
258
+ })
259
+
260
+ it('merges a customers override with populated segments into resolved model', () => {
261
+ const model = resolveOrganizationModel({
262
+ customers: {
263
+ segments: [
264
+ {
265
+ id: 'seg-agencies',
266
+ name: 'Agency Owners',
267
+ pains: ['Too much manual work'],
268
+ gains: ['More client capacity'],
269
+ valueProp: 'Automate the repetitive parts of agency ops.'
270
+ }
271
+ ]
272
+ }
273
+ })
274
+ expect(model.customers.segments).toHaveLength(1)
275
+ expect(model.customers.segments[0].id).toBe('seg-agencies')
276
+ expect(model.customers.segments[0].pains).toEqual(['Too much manual work'])
277
+ expect(model.customers.segments[0].description).toBe('')
278
+ })
279
+
280
+ it('does not bleed customers changes into other top-level domains', () => {
281
+ const model = resolveOrganizationModel({
282
+ customers: { segments: [{ id: 'seg-isolated' }] }
283
+ })
284
+ expect(model.identity).toBeDefined()
285
+ expect(model.features).toBeDefined()
286
+ expect(model.statuses).toBeDefined()
287
+ expect(model.navigation).toBeDefined()
288
+ })
289
+
290
+ it('omitting customers key entirely resolves to default empty segments', () => {
291
+ const model = resolveOrganizationModel({})
292
+ expect(model.customers).toBeDefined()
293
+ expect(model.customers.segments).toEqual([])
294
+ })
295
+ })