@elevasis/core 0.4.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 (67) hide show
  1. package/dist/business/entities-published.d.ts +215 -0
  2. package/dist/business/entities-published.js +69 -0
  3. package/dist/index.d.ts +436 -42
  4. package/dist/index.js +601 -50
  5. package/dist/organization-model/index.d.ts +436 -42
  6. package/dist/organization-model/index.js +601 -50
  7. package/package.json +7 -3
  8. package/src/__tests__/publish.test.ts +1 -1
  9. package/src/__tests__/template-foundations-compatibility.test.ts +2 -2
  10. package/src/business/README.md +52 -0
  11. package/src/business/__tests__/entities-published.test.ts +33 -0
  12. package/src/business/acquisition/types.ts +2 -0
  13. package/src/business/entities-published.ts +24 -0
  14. package/src/commands/queue/types/task.ts +3 -3
  15. package/src/execution/engine/index.ts +8 -0
  16. package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +2 -3
  17. package/src/execution/engine/tools/registry.ts +26 -24
  18. package/src/execution/engine/tools/tool-maps.ts +13 -9
  19. package/src/execution/engine/workflow/types.ts +2 -3
  20. package/src/organization-model/README.md +16 -12
  21. package/src/organization-model/__tests__/defaults.test.ts +175 -0
  22. package/src/organization-model/__tests__/domains/customers.test.ts +295 -0
  23. package/src/organization-model/__tests__/domains/goals.test.ts +479 -0
  24. package/src/organization-model/__tests__/domains/identity.test.ts +278 -0
  25. package/src/organization-model/__tests__/domains/navigation.test.ts +212 -0
  26. package/src/organization-model/__tests__/domains/offerings.test.ts +419 -0
  27. package/src/organization-model/__tests__/domains/operations.test.ts +203 -0
  28. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +362 -0
  29. package/src/organization-model/__tests__/domains/roles.test.ts +347 -0
  30. package/src/organization-model/__tests__/domains/statuses.test.ts +243 -0
  31. package/src/organization-model/__tests__/foundation.test.ts +105 -0
  32. package/src/organization-model/__tests__/resolve.test.ts +447 -3
  33. package/src/organization-model/__tests__/schema.test.ts +407 -0
  34. package/src/organization-model/contracts.ts +12 -1
  35. package/src/organization-model/defaults.ts +53 -17
  36. package/src/organization-model/domains/customers.ts +75 -0
  37. package/src/organization-model/domains/goals.ts +80 -0
  38. package/src/organization-model/domains/identity.ts +87 -0
  39. package/src/organization-model/domains/navigation.ts +43 -4
  40. package/src/organization-model/domains/offerings.ts +66 -0
  41. package/src/organization-model/domains/operations.ts +85 -0
  42. package/src/organization-model/domains/{delivery.ts → projects.ts} +6 -6
  43. package/src/organization-model/domains/{lead-gen.ts → prospecting.ts} +5 -5
  44. package/src/organization-model/domains/roles.ts +55 -0
  45. package/src/organization-model/domains/sales.ts +94 -0
  46. package/src/organization-model/domains/shared.ts +30 -1
  47. package/src/organization-model/domains/statuses.ts +130 -0
  48. package/src/organization-model/foundation.ts +3 -3
  49. package/src/organization-model/index.ts +3 -3
  50. package/src/organization-model/organization-graph.mdx +1 -0
  51. package/src/organization-model/organization-model.mdx +84 -19
  52. package/src/organization-model/published.ts +62 -4
  53. package/src/organization-model/schema.ts +67 -7
  54. package/src/organization-model/types.ts +31 -7
  55. package/src/platform/constants/versions.ts +1 -1
  56. package/src/platform/registry/types.ts +1 -1
  57. package/src/projects/api-schemas.ts +1 -0
  58. package/src/reference/_generated/contracts.md +116 -8
  59. package/src/reference/glossary.md +25 -4
  60. package/src/requests/__tests__/api-schemas.test.ts +277 -0
  61. package/src/requests/api-schemas.ts +83 -0
  62. package/src/requests/index.ts +1 -0
  63. package/src/supabase/database.types.ts +88 -0
  64. package/src/organization-model/domains/crm.ts +0 -46
  65. /package/src/business/{delivery → projects}/index.ts +0 -0
  66. /package/src/business/{delivery → projects}/types.ts +0 -0
  67. /package/src/business/{crm → sales}/api-schemas.ts +0 -0
@@ -0,0 +1,243 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import type { QueueTaskStatus } from '../../../commands/queue/types/task'
3
+ import type { TaskStatus } from '../../../business/projects/types'
4
+ import {
5
+ DEFAULT_ORGANIZATION_MODEL_STATUSES,
6
+ StatusEntrySchema,
7
+ StatusesDomainSchema,
8
+ StatusSemanticClassSchema
9
+ } from '../../domains/statuses'
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Group 1: StatusEntrySchema — positive parse
13
+ // ---------------------------------------------------------------------------
14
+
15
+ describe('StatusEntrySchema — positive parse', () => {
16
+ it('accepts a fully-specified entry (id, label, semanticClass, category)', () => {
17
+ const result = StatusEntrySchema.safeParse({
18
+ id: 'delivery.task.planned',
19
+ label: 'Planned',
20
+ semanticClass: 'delivery.task',
21
+ category: 'delivery'
22
+ })
23
+ expect(result.success).toBe(true)
24
+ })
25
+
26
+ it('accepts a minimal entry without category (category is optional)', () => {
27
+ const result = StatusEntrySchema.safeParse({
28
+ id: 'queue.pending',
29
+ label: 'Pending',
30
+ semanticClass: 'queue'
31
+ })
32
+ expect(result.success).toBe(true)
33
+ if (result.success) {
34
+ expect(result.data.category).toBeUndefined()
35
+ }
36
+ })
37
+
38
+ it('trims whitespace from id and label', () => {
39
+ const result = StatusEntrySchema.safeParse({
40
+ id: ' trimmed-id ',
41
+ label: ' Trimmed Label ',
42
+ semanticClass: 'execution'
43
+ })
44
+ expect(result.success).toBe(true)
45
+ if (result.success) {
46
+ expect(result.data.id).toBe('trimmed-id')
47
+ expect(result.data.label).toBe('Trimmed Label')
48
+ }
49
+ })
50
+ })
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Group 2: StatusEntrySchema — negative parse
54
+ // ---------------------------------------------------------------------------
55
+
56
+ describe('StatusEntrySchema — negative parse', () => {
57
+ it('rejects a missing id field', () => {
58
+ const result = StatusEntrySchema.safeParse({
59
+ label: 'No ID',
60
+ semanticClass: 'queue'
61
+ })
62
+ expect(result.success).toBe(false)
63
+ })
64
+
65
+ it('rejects a missing label field', () => {
66
+ const result = StatusEntrySchema.safeParse({
67
+ id: 'queue.pending',
68
+ semanticClass: 'queue'
69
+ })
70
+ expect(result.success).toBe(false)
71
+ })
72
+
73
+ it('rejects a missing semanticClass field', () => {
74
+ const result = StatusEntrySchema.safeParse({
75
+ id: 'queue.pending',
76
+ label: 'Pending'
77
+ })
78
+ expect(result.success).toBe(false)
79
+ })
80
+
81
+ it('rejects an unknown semanticClass value', () => {
82
+ const result = StatusEntrySchema.safeParse({
83
+ id: 'some.entry',
84
+ label: 'Some Entry',
85
+ semanticClass: 'not-a-real-class'
86
+ })
87
+ expect(result.success).toBe(false)
88
+ })
89
+
90
+ it('rejects a non-string id (number)', () => {
91
+ const result = StatusEntrySchema.safeParse({
92
+ id: 42,
93
+ label: 'Label',
94
+ semanticClass: 'queue'
95
+ })
96
+ expect(result.success).toBe(false)
97
+ })
98
+
99
+ it('rejects an empty string label (min length 1 after trim)', () => {
100
+ const result = StatusEntrySchema.safeParse({
101
+ id: 'some.entry',
102
+ label: ' ',
103
+ semanticClass: 'queue'
104
+ })
105
+ expect(result.success).toBe(false)
106
+ })
107
+ })
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Group 3: StatusesDomainSchema — structural tests
111
+ // ---------------------------------------------------------------------------
112
+
113
+ describe('StatusesDomainSchema — structural tests', () => {
114
+ it('accepts an entries-empty domain object', () => {
115
+ const result = StatusesDomainSchema.safeParse({ entries: [] })
116
+ expect(result.success).toBe(true)
117
+ })
118
+
119
+ it('applies an empty-array default when entries is omitted', () => {
120
+ const result = StatusesDomainSchema.safeParse({})
121
+ expect(result.success).toBe(true)
122
+ if (result.success) {
123
+ expect(result.data.entries).toEqual([])
124
+ }
125
+ })
126
+ })
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Group 4: Semantic class enum — all 8 values declared
130
+ // ---------------------------------------------------------------------------
131
+
132
+ describe('StatusSemanticClassSchema — enum coverage', () => {
133
+ const expectedClasses = [
134
+ 'delivery.task',
135
+ 'delivery.project',
136
+ 'delivery.milestone',
137
+ 'queue',
138
+ 'execution',
139
+ 'schedule',
140
+ 'schedule.run',
141
+ 'request'
142
+ ] as const
143
+
144
+ it('enum exposes exactly 8 semantic classes', () => {
145
+ expect(StatusSemanticClassSchema.options).toHaveLength(8)
146
+ })
147
+
148
+ it.each(expectedClasses)('"%s" is a valid semanticClass value', (cls) => {
149
+ const result = StatusSemanticClassSchema.safeParse(cls)
150
+ expect(result.success).toBe(true)
151
+ })
152
+
153
+ it('every entry in the seed uses one of the 8 known semanticClass values', () => {
154
+ const valid = new Set<string>(StatusSemanticClassSchema.options)
155
+ for (const entry of DEFAULT_ORGANIZATION_MODEL_STATUSES.entries) {
156
+ expect(
157
+ valid.has(entry.semanticClass),
158
+ `Unexpected semanticClass "${entry.semanticClass}" on entry "${entry.id}"`
159
+ ).toBe(true)
160
+ }
161
+ })
162
+
163
+ it('all 8 semanticClass values have at least one seed entry', () => {
164
+ const presentClasses = new Set(DEFAULT_ORGANIZATION_MODEL_STATUSES.entries.map((e) => e.semanticClass))
165
+ for (const cls of StatusSemanticClassSchema.options) {
166
+ expect(presentClasses.has(cls), `No seed entry found for semanticClass "${cls}"`).toBe(true)
167
+ }
168
+ })
169
+ })
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Group 5: Seed completeness — per-group counts
173
+ // ---------------------------------------------------------------------------
174
+
175
+ describe('DEFAULT_ORGANIZATION_MODEL_STATUSES seed — per-group counts', () => {
176
+ function countBySemClass(cls: string) {
177
+ return DEFAULT_ORGANIZATION_MODEL_STATUSES.entries.filter((e) => e.semanticClass === cls).length
178
+ }
179
+
180
+ it('has 6 delivery.project entries', () => {
181
+ expect(countBySemClass('delivery.project')).toBe(6)
182
+ })
183
+
184
+ it('has 5 delivery.milestone entries', () => {
185
+ expect(countBySemClass('delivery.milestone')).toBe(5)
186
+ })
187
+
188
+ it('has 5 execution entries', () => {
189
+ expect(countBySemClass('execution')).toBe(5)
190
+ })
191
+
192
+ it('has 4 schedule entries', () => {
193
+ expect(countBySemClass('schedule')).toBe(4)
194
+ })
195
+
196
+ it('has 4 schedule.run entries', () => {
197
+ expect(countBySemClass('schedule.run')).toBe(4)
198
+ })
199
+
200
+ it('has 4 request entries', () => {
201
+ expect(countBySemClass('request')).toBe(4)
202
+ })
203
+ })
204
+
205
+ // ---------------------------------------------------------------------------
206
+ // Group 6: QueueTaskStatus values — all 5 present in seed with class "queue"
207
+ // ---------------------------------------------------------------------------
208
+
209
+ describe('QueueTaskStatus coverage in seed', () => {
210
+ const queueValues: QueueTaskStatus[] = ['pending', 'processing', 'completed', 'failed', 'expired']
211
+ const queueIds = DEFAULT_ORGANIZATION_MODEL_STATUSES.entries
212
+ .filter((e) => e.semanticClass === 'queue')
213
+ .map((e) => e.id)
214
+
215
+ it.each(queueValues)('queue.%s is present as a seed entry id', (status) => {
216
+ expect(queueIds).toContain(`queue.${status}`)
217
+ })
218
+ })
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // Group 7: Delivery TaskStatus values — all 9 present in seed with class "delivery.task"
222
+ // ---------------------------------------------------------------------------
223
+
224
+ describe('TaskStatus (delivery) coverage in seed', () => {
225
+ const taskValues: TaskStatus[] = [
226
+ 'planned',
227
+ 'in_progress',
228
+ 'blocked',
229
+ 'submitted',
230
+ 'approved',
231
+ 'revision_requested',
232
+ 'rejected',
233
+ 'cancelled',
234
+ 'completed'
235
+ ]
236
+ const taskIds = DEFAULT_ORGANIZATION_MODEL_STATUSES.entries
237
+ .filter((e) => e.semanticClass === 'delivery.task')
238
+ .map((e) => e.id)
239
+
240
+ it.each(taskValues)('delivery.task.%s is present as a seed entry id', (status) => {
241
+ expect(taskIds).toContain(`delivery.task.${status}`)
242
+ })
243
+ })
@@ -0,0 +1,105 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { createFoundationOrganizationModel } from '../foundation'
3
+
4
+ describe('createFoundationOrganizationModel', () => {
5
+ it('builds the foundation model from a branding-only override', () => {
6
+ const result = createFoundationOrganizationModel({
7
+ branding: { organizationName: 'Acme', productName: 'Acme OS', shortName: 'Acme' }
8
+ })
9
+
10
+ expect(result.canonical.branding.organizationName).toBe('Acme')
11
+ expect(result.homeLabel).toBe('Dashboard')
12
+ expect(result.model.navigation.defaultSurfaceId).toBe('operations')
13
+
14
+ const surfaces = result.model.navigation.surfaces
15
+ expect(surfaces.find((s) => s.id === 'crm')).toBeDefined()
16
+ expect(surfaces.find((s) => s.id === 'lead-gen')).toBeDefined()
17
+ expect(surfaces.find((s) => s.id === 'projects')).toBeDefined()
18
+ expect(surfaces.find((s) => s.id === 'operations')).toBeDefined()
19
+ expect(surfaces.find((s) => s.id === 'settings')).toBeDefined()
20
+
21
+ expect(result.quickAccessSurfaceIds).toContain('operations')
22
+ expect(result.quickAccessSurfaceIds).toContain('projects')
23
+ expect(result.quickAccessSurfaceIds).toContain('lead-gen')
24
+ expect(result.quickAccessSurfaceIds).toContain('crm')
25
+ })
26
+
27
+ it('passes deeper overrides through to the canonical model', () => {
28
+ const result = createFoundationOrganizationModel({
29
+ branding: { organizationName: 'Acme', productName: 'Acme OS', shortName: 'Acme' },
30
+ sales: {
31
+ pipelines: [
32
+ {
33
+ id: 'custom-pipeline',
34
+ label: 'Custom Pipeline',
35
+ description: 'A custom pipeline',
36
+ entityId: 'crm.deal',
37
+ stages: [
38
+ {
39
+ id: 'stage-1',
40
+ label: 'Stage 1',
41
+ color: 'blue',
42
+ order: 0,
43
+ semanticClass: 'open' as const,
44
+ surfaceIds: ['crm.pipeline'],
45
+ resourceIds: []
46
+ }
47
+ ]
48
+ }
49
+ ]
50
+ }
51
+ })
52
+
53
+ expect(result.canonical.sales.pipelines).toHaveLength(1)
54
+ expect(result.canonical.sales.pipelines[0]?.id).toBe('custom-pipeline')
55
+ })
56
+
57
+ it('exposes a working getOrganizationSurface lookup', () => {
58
+ const result = createFoundationOrganizationModel({
59
+ branding: { organizationName: 'Acme', productName: 'Acme OS', shortName: 'Acme' }
60
+ })
61
+
62
+ const crmSurface = result.getOrganizationSurface('crm')
63
+ expect(crmSurface).toBeDefined()
64
+ expect(crmSurface?.id).toBe('crm')
65
+ expect(crmSurface?.icon).toBe('crm')
66
+
67
+ expect(result.getOrganizationSurface('nonexistent')).toBeUndefined()
68
+ })
69
+
70
+ it('throws when a required core surface is missing', () => {
71
+ const override = {
72
+ features: [
73
+ {
74
+ id: 'crm',
75
+ label: 'CRM',
76
+ description: 'CRM workspace',
77
+ enabled: true,
78
+ color: 'blue',
79
+ entityIds: [],
80
+ surfaceIds: ['custom.home'],
81
+ resourceIds: [],
82
+ capabilityIds: []
83
+ }
84
+ ],
85
+ navigation: {
86
+ defaultSurfaceId: 'custom.home',
87
+ surfaces: [
88
+ {
89
+ id: 'custom.home',
90
+ label: 'Home',
91
+ path: '/home',
92
+ surfaceType: 'page' as const,
93
+ featureIds: ['crm'],
94
+ entityIds: [],
95
+ resourceIds: [],
96
+ capabilityIds: []
97
+ }
98
+ ],
99
+ groups: [{ id: 'primary', label: 'Primary', placement: 'primary', surfaceIds: ['custom.home'] }]
100
+ }
101
+ }
102
+
103
+ expect(() => createFoundationOrganizationModel(override)).toThrow(/Missing organization surface/)
104
+ })
105
+ })