@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,278 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ BusinessHoursDaySchema,
4
+ BusinessHoursSchema,
5
+ DEFAULT_ORGANIZATION_MODEL_IDENTITY,
6
+ IdentityDomainSchema
7
+ } from '../../domains/identity'
8
+ import { resolveOrganizationModel } from '../../resolve'
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Group 1: IdentityDomainSchema — positive parse
12
+ // ---------------------------------------------------------------------------
13
+
14
+ describe('IdentityDomainSchema — positive parse', () => {
15
+ it('accepts a fully-populated identity object', () => {
16
+ const result = IdentityDomainSchema.safeParse({
17
+ mission: 'Help businesses automate complex work.',
18
+ vision: 'A world where every team has an AI co-pilot.',
19
+ legalName: 'Acme Corp LLC',
20
+ entityType: 'LLC',
21
+ jurisdiction: 'United States – Delaware',
22
+ industryCategory: 'Software / SaaS',
23
+ geographicFocus: 'North America',
24
+ timeZone: 'America/Los_Angeles',
25
+ businessHours: {
26
+ monday: { open: '09:00', close: '17:00' },
27
+ friday: { open: '09:00', close: '15:00' }
28
+ }
29
+ })
30
+ expect(result.success).toBe(true)
31
+ })
32
+
33
+ it('accepts a minimal input (empty object) and applies all defaults', () => {
34
+ const result = IdentityDomainSchema.safeParse({})
35
+ expect(result.success).toBe(true)
36
+ if (result.success) {
37
+ expect(result.data.mission).toBe('')
38
+ expect(result.data.vision).toBe('')
39
+ expect(result.data.legalName).toBe('')
40
+ expect(result.data.entityType).toBe('')
41
+ expect(result.data.jurisdiction).toBe('')
42
+ expect(result.data.industryCategory).toBe('')
43
+ expect(result.data.geographicFocus).toBe('')
44
+ expect(result.data.timeZone).toBe('UTC')
45
+ expect(result.data.businessHours).toEqual({})
46
+ }
47
+ })
48
+
49
+ it('accepts a partial object — only mission and timeZone provided', () => {
50
+ const result = IdentityDomainSchema.safeParse({
51
+ mission: 'Ship great software.',
52
+ timeZone: 'Europe/London'
53
+ })
54
+ expect(result.success).toBe(true)
55
+ if (result.success) {
56
+ expect(result.data.mission).toBe('Ship great software.')
57
+ expect(result.data.timeZone).toBe('Europe/London')
58
+ expect(result.data.vision).toBe('')
59
+ }
60
+ })
61
+
62
+ it('trims leading/trailing whitespace from string fields', () => {
63
+ const result = IdentityDomainSchema.safeParse({
64
+ mission: ' Trimmed mission. ',
65
+ legalName: ' Trimmed Name '
66
+ })
67
+ expect(result.success).toBe(true)
68
+ if (result.success) {
69
+ expect(result.data.mission).toBe('Trimmed mission.')
70
+ expect(result.data.legalName).toBe('Trimmed Name')
71
+ }
72
+ })
73
+ })
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Group 2: Default values
77
+ // ---------------------------------------------------------------------------
78
+
79
+ describe('IdentityDomainSchema — default values', () => {
80
+ it('timeZone defaults to "UTC"', () => {
81
+ const result = IdentityDomainSchema.safeParse({})
82
+ expect(result.success).toBe(true)
83
+ if (result.success) {
84
+ expect(result.data.timeZone).toBe('UTC')
85
+ }
86
+ })
87
+
88
+ it('businessHours defaults to empty object', () => {
89
+ const result = IdentityDomainSchema.safeParse({})
90
+ expect(result.success).toBe(true)
91
+ if (result.success) {
92
+ expect(result.data.businessHours).toEqual({})
93
+ }
94
+ })
95
+
96
+ it('all string fields default to empty string', () => {
97
+ const result = IdentityDomainSchema.safeParse({})
98
+ expect(result.success).toBe(true)
99
+ if (result.success) {
100
+ const stringDefaults: (keyof typeof result.data)[] = [
101
+ 'mission',
102
+ 'vision',
103
+ 'legalName',
104
+ 'entityType',
105
+ 'jurisdiction',
106
+ 'industryCategory',
107
+ 'geographicFocus'
108
+ ]
109
+ for (const field of stringDefaults) {
110
+ expect(result.data[field], `field "${field}" should default to ""`).toBe('')
111
+ }
112
+ }
113
+ })
114
+
115
+ it('DEFAULT_ORGANIZATION_MODEL_IDENTITY constant matches schema parse of empty object', () => {
116
+ const result = IdentityDomainSchema.safeParse({})
117
+ expect(result.success).toBe(true)
118
+ if (result.success) {
119
+ expect(result.data).toEqual(DEFAULT_ORGANIZATION_MODEL_IDENTITY)
120
+ }
121
+ })
122
+ })
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Group 3: BusinessHoursSchema — valid shapes
126
+ // ---------------------------------------------------------------------------
127
+
128
+ describe('BusinessHoursSchema — valid shapes', () => {
129
+ it('accepts all seven days populated', () => {
130
+ const result = BusinessHoursSchema.safeParse({
131
+ monday: { open: '08:00', close: '17:00' },
132
+ tuesday: { open: '08:00', close: '17:00' },
133
+ wednesday: { open: '08:00', close: '17:00' },
134
+ thursday: { open: '08:00', close: '17:00' },
135
+ friday: { open: '08:00', close: '17:00' },
136
+ saturday: { open: '10:00', close: '14:00' },
137
+ sunday: { open: '10:00', close: '14:00' }
138
+ })
139
+ expect(result.success).toBe(true)
140
+ })
141
+
142
+ it('accepts an object with only some days present (others are optional)', () => {
143
+ const result = BusinessHoursSchema.safeParse({
144
+ monday: { open: '09:00', close: '18:00' }
145
+ })
146
+ expect(result.success).toBe(true)
147
+ if (result.success) {
148
+ expect(result.data.tuesday).toBeUndefined()
149
+ expect(result.data.sunday).toBeUndefined()
150
+ }
151
+ })
152
+
153
+ it('defaults to empty object when input is undefined (schema .default({}))', () => {
154
+ const result = BusinessHoursSchema.safeParse(undefined)
155
+ expect(result.success).toBe(true)
156
+ if (result.success) {
157
+ expect(result.data).toEqual({})
158
+ }
159
+ })
160
+ })
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Group 4: BusinessHoursDaySchema — HH:MM format validation
164
+ // ---------------------------------------------------------------------------
165
+
166
+ describe('BusinessHoursDaySchema — HH:MM regex', () => {
167
+ it('accepts valid 24-hour HH:MM values', () => {
168
+ const result = BusinessHoursDaySchema.safeParse({ open: '00:00', close: '23:59' })
169
+ expect(result.success).toBe(true)
170
+ })
171
+
172
+ it('rejects time without leading zero (9:00 instead of 09:00)', () => {
173
+ const result = BusinessHoursDaySchema.safeParse({ open: '9:00', close: '17:00' })
174
+ expect(result.success).toBe(false)
175
+ })
176
+
177
+ it('rejects time with seconds appended (HH:MM:SS)', () => {
178
+ const result = BusinessHoursDaySchema.safeParse({ open: '09:00:00', close: '17:00:00' })
179
+ expect(result.success).toBe(false)
180
+ })
181
+
182
+ it('rejects time with AM/PM suffix', () => {
183
+ const result = BusinessHoursDaySchema.safeParse({ open: '09:00AM', close: '05:00PM' })
184
+ expect(result.success).toBe(false)
185
+ })
186
+
187
+ it('rejects missing close field', () => {
188
+ const result = BusinessHoursDaySchema.safeParse({ open: '09:00' })
189
+ expect(result.success).toBe(false)
190
+ })
191
+
192
+ it('rejects non-string open value (number)', () => {
193
+ const result = BusinessHoursDaySchema.safeParse({ open: 900, close: '17:00' })
194
+ expect(result.success).toBe(false)
195
+ })
196
+ })
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // Group 5: IdentityDomainSchema — negative parse (wrong types / over-max)
200
+ // ---------------------------------------------------------------------------
201
+
202
+ describe('IdentityDomainSchema — negative parse', () => {
203
+ it('rejects mission as a number', () => {
204
+ const result = IdentityDomainSchema.safeParse({ mission: 42 })
205
+ expect(result.success).toBe(false)
206
+ })
207
+
208
+ it('rejects timeZone as a number', () => {
209
+ const result = IdentityDomainSchema.safeParse({ timeZone: 0 })
210
+ expect(result.success).toBe(false)
211
+ })
212
+
213
+ it('rejects mission exceeding 1000 characters', () => {
214
+ const result = IdentityDomainSchema.safeParse({ mission: 'x'.repeat(1001) })
215
+ expect(result.success).toBe(false)
216
+ })
217
+
218
+ it('rejects legalName exceeding 200 characters', () => {
219
+ const result = IdentityDomainSchema.safeParse({ legalName: 'x'.repeat(201) })
220
+ expect(result.success).toBe(false)
221
+ })
222
+
223
+ it('rejects entityType exceeding 100 characters', () => {
224
+ const result = IdentityDomainSchema.safeParse({ entityType: 'x'.repeat(101) })
225
+ expect(result.success).toBe(false)
226
+ })
227
+
228
+ it('rejects timeZone exceeding 100 characters', () => {
229
+ const result = IdentityDomainSchema.safeParse({ timeZone: 'x'.repeat(101) })
230
+ expect(result.success).toBe(false)
231
+ })
232
+ })
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // Group 6: Integration — resolveOrganizationModel
236
+ // ---------------------------------------------------------------------------
237
+
238
+ describe('resolveOrganizationModel — identity domain integration', () => {
239
+ it('merges partial identity override and preserves unset fields as defaults', () => {
240
+ const model = resolveOrganizationModel({
241
+ identity: { mission: 'Build the future.' }
242
+ })
243
+ expect(model.identity.mission).toBe('Build the future.')
244
+ expect(model.identity.timeZone).toBe('UTC')
245
+ expect(model.identity.vision).toBe('')
246
+ expect(model.identity.businessHours).toEqual({})
247
+ })
248
+
249
+ it('fully-specified identity override round-trips through resolveOrganizationModel', () => {
250
+ const override = {
251
+ identity: {
252
+ mission: 'Automate everything.',
253
+ vision: 'Zero manual work.',
254
+ legalName: 'Test Corp Inc',
255
+ entityType: 'Corporation',
256
+ jurisdiction: 'United States – California',
257
+ industryCategory: 'Software / SaaS',
258
+ geographicFocus: 'Global',
259
+ timeZone: 'America/Chicago',
260
+ businessHours: {
261
+ monday: { open: '09:00', close: '17:00' }
262
+ }
263
+ }
264
+ }
265
+ const model = resolveOrganizationModel(override)
266
+ expect(model.identity).toEqual(override.identity)
267
+ })
268
+
269
+ it('does not bleed identity changes into other top-level domains', () => {
270
+ const model = resolveOrganizationModel({
271
+ identity: { mission: 'Isolated change.' }
272
+ })
273
+ // navigation, features, statuses etc. must still be present
274
+ expect(model.navigation).toBeDefined()
275
+ expect(model.features).toBeDefined()
276
+ expect(model.statuses).toBeDefined()
277
+ })
278
+ })
@@ -0,0 +1,212 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ CORE_PLACEMENT_VALUES,
4
+ NavigationGroupSchema,
5
+ OrganizationModelNavigationSchema,
6
+ SurfaceTypeSchema
7
+ } from '../../domains/navigation'
8
+ import { ResourceMappingSchema } from '../../domains/shared'
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Group 1: Default placement values parse (core baseline)
12
+ // ---------------------------------------------------------------------------
13
+
14
+ describe('NavigationGroupSchema — core placement values', () => {
15
+ it.each(CORE_PLACEMENT_VALUES)('"%s" is accepted as a placement value', (placement) => {
16
+ const result = NavigationGroupSchema.safeParse({
17
+ id: 'test-group',
18
+ label: 'Test Group',
19
+ placement,
20
+ surfaceIds: []
21
+ })
22
+ expect(result.success).toBe(true)
23
+ })
24
+ })
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Group 2: Extension placement values parse (open string pattern)
28
+ // ---------------------------------------------------------------------------
29
+
30
+ describe('NavigationGroupSchema — extension placement values', () => {
31
+ it('accepts "pinned" as an extension placement value', () => {
32
+ const result = NavigationGroupSchema.safeParse({
33
+ id: 'pinned-group',
34
+ label: 'Pinned',
35
+ placement: 'pinned',
36
+ surfaceIds: []
37
+ })
38
+ expect(result.success).toBe(true)
39
+ })
40
+
41
+ it('accepts "spotlight" as an extension placement value', () => {
42
+ const result = NavigationGroupSchema.safeParse({
43
+ id: 'spotlight-group',
44
+ label: 'Spotlight',
45
+ placement: 'spotlight',
46
+ surfaceIds: []
47
+ })
48
+ expect(result.success).toBe(true)
49
+ })
50
+
51
+ it('accepts "contextual" as an extension placement value', () => {
52
+ const result = NavigationGroupSchema.safeParse({
53
+ id: 'ctx-group',
54
+ label: 'Contextual',
55
+ placement: 'contextual',
56
+ surfaceIds: []
57
+ })
58
+ expect(result.success).toBe(true)
59
+ })
60
+
61
+ it('trims whitespace from extension placement values', () => {
62
+ const result = NavigationGroupSchema.safeParse({
63
+ id: 'trim-group',
64
+ label: 'Trimmed',
65
+ placement: ' pinned ',
66
+ surfaceIds: []
67
+ })
68
+ expect(result.success).toBe(true)
69
+ if (result.success) {
70
+ expect(result.data.placement).toBe('pinned')
71
+ }
72
+ })
73
+ })
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Group 3: Placement rejection cases (non-string, empty)
77
+ // ---------------------------------------------------------------------------
78
+
79
+ describe('NavigationGroupSchema — placement rejection cases', () => {
80
+ it('rejects a numeric placement value', () => {
81
+ const result = NavigationGroupSchema.safeParse({
82
+ id: 'num-group',
83
+ label: 'Numeric',
84
+ placement: 42,
85
+ surfaceIds: []
86
+ })
87
+ expect(result.success).toBe(false)
88
+ })
89
+
90
+ it('rejects null as a placement value', () => {
91
+ const result = NavigationGroupSchema.safeParse({
92
+ id: 'null-group',
93
+ label: 'Null',
94
+ placement: null,
95
+ surfaceIds: []
96
+ })
97
+ expect(result.success).toBe(false)
98
+ })
99
+
100
+ it('rejects an empty string placement (min length 1 after trim)', () => {
101
+ const result = NavigationGroupSchema.safeParse({
102
+ id: 'empty-group',
103
+ label: 'Empty',
104
+ placement: ' ',
105
+ surfaceIds: []
106
+ })
107
+ expect(result.success).toBe(false)
108
+ })
109
+
110
+ it('rejects a missing placement field', () => {
111
+ const result = NavigationGroupSchema.safeParse({
112
+ id: 'no-placement-group',
113
+ label: 'No Placement',
114
+ surfaceIds: []
115
+ })
116
+ expect(result.success).toBe(false)
117
+ })
118
+ })
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // Group 4: surfaceType remains CLOSED (closed enum regression guard)
122
+ // ---------------------------------------------------------------------------
123
+
124
+ describe('SurfaceTypeSchema — closed enum regression guard', () => {
125
+ const validSurfaceTypes = ['page', 'dashboard', 'graph', 'detail', 'list', 'settings'] as const
126
+
127
+ it.each(validSurfaceTypes)('"%s" is a valid surfaceType', (surfaceType) => {
128
+ const result = SurfaceTypeSchema.safeParse(surfaceType)
129
+ expect(result.success).toBe(true)
130
+ })
131
+
132
+ it('rejects an unknown surfaceType value', () => {
133
+ const result = SurfaceTypeSchema.safeParse('spotlight')
134
+ expect(result.success).toBe(false)
135
+ })
136
+
137
+ it('rejects a numeric surfaceType value', () => {
138
+ const result = SurfaceTypeSchema.safeParse(1)
139
+ expect(result.success).toBe(false)
140
+ })
141
+ })
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // Group 5: resourceType remains CLOSED (closed enum regression guard)
145
+ // ---------------------------------------------------------------------------
146
+
147
+ describe('ResourceMappingSchema resourceType — closed enum regression guard', () => {
148
+ const validResourceTypes = ['workflow', 'agent', 'trigger', 'integration', 'external', 'human_checkpoint'] as const
149
+
150
+ it.each(validResourceTypes)('"%s" is a valid resourceType', (resourceType) => {
151
+ const result = ResourceMappingSchema.safeParse({
152
+ id: 'rm-test',
153
+ label: 'Test Resource',
154
+ resourceId: 'res-1',
155
+ resourceType,
156
+ featureIds: [],
157
+ entityIds: [],
158
+ surfaceIds: [],
159
+ capabilityIds: []
160
+ })
161
+ expect(result.success).toBe(true)
162
+ })
163
+
164
+ it('rejects an unknown resourceType value', () => {
165
+ const result = ResourceMappingSchema.safeParse({
166
+ id: 'rm-ext',
167
+ label: 'Extended Resource',
168
+ resourceId: 'res-ext',
169
+ resourceType: 'custom-handler',
170
+ featureIds: [],
171
+ entityIds: [],
172
+ surfaceIds: [],
173
+ capabilityIds: []
174
+ })
175
+ expect(result.success).toBe(false)
176
+ })
177
+ })
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // Group 6: OrganizationModelNavigationSchema — extension placement in a full group
181
+ // ---------------------------------------------------------------------------
182
+
183
+ describe('OrganizationModelNavigationSchema — extension placement roundtrip', () => {
184
+ it('parses a navigation model where a group uses an extension placement value', () => {
185
+ const result = OrganizationModelNavigationSchema.safeParse({
186
+ surfaces: [
187
+ {
188
+ id: 'home',
189
+ label: 'Home',
190
+ path: '/home',
191
+ surfaceType: 'page',
192
+ featureIds: [],
193
+ entityIds: [],
194
+ resourceIds: [],
195
+ capabilityIds: []
196
+ }
197
+ ],
198
+ groups: [
199
+ {
200
+ id: 'pinned-group',
201
+ label: 'Pinned',
202
+ placement: 'pinned',
203
+ surfaceIds: ['home']
204
+ }
205
+ ]
206
+ })
207
+ expect(result.success).toBe(true)
208
+ if (result.success) {
209
+ expect(result.data.groups[0].placement).toBe('pinned')
210
+ }
211
+ })
212
+ })