@elevasis/core 0.5.0 → 0.7.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 (66) hide show
  1. package/dist/index.d.ts +428 -42
  2. package/dist/index.js +596 -47
  3. package/dist/organization-model/index.d.ts +428 -42
  4. package/dist/organization-model/index.js +596 -47
  5. package/package.json +4 -3
  6. package/src/__tests__/template-foundations-compatibility.test.ts +2 -2
  7. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +1131 -0
  8. package/src/_gen/__tests__/scaffold-contracts.test.ts +53 -0
  9. package/src/_gen/scaffold-contracts.ts +45 -0
  10. package/src/business/acquisition/types.ts +2 -0
  11. package/src/commands/queue/types/task.ts +3 -3
  12. package/src/execution/engine/index.ts +8 -0
  13. package/src/execution/engine/tools/registry.ts +26 -24
  14. package/src/execution/engine/tools/tool-maps.ts +13 -9
  15. package/src/execution/engine/workflow/types.ts +2 -3
  16. package/src/index.ts +10 -0
  17. package/src/organization-model/README.md +16 -12
  18. package/src/organization-model/__tests__/defaults.test.ts +175 -0
  19. package/src/organization-model/__tests__/domains/customers.test.ts +295 -0
  20. package/src/organization-model/__tests__/domains/goals.test.ts +479 -0
  21. package/src/organization-model/__tests__/domains/identity.test.ts +279 -0
  22. package/src/organization-model/__tests__/domains/navigation.test.ts +212 -0
  23. package/src/organization-model/__tests__/domains/offerings.test.ts +419 -0
  24. package/src/organization-model/__tests__/domains/operations.test.ts +203 -0
  25. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +362 -0
  26. package/src/organization-model/__tests__/domains/roles.test.ts +347 -0
  27. package/src/organization-model/__tests__/domains/statuses.test.ts +243 -0
  28. package/src/organization-model/__tests__/foundation.test.ts +3 -3
  29. package/src/organization-model/__tests__/resolve.test.ts +447 -3
  30. package/src/organization-model/__tests__/schema.test.ts +407 -0
  31. package/src/organization-model/contracts.ts +5 -5
  32. package/src/organization-model/defaults.ts +39 -16
  33. package/src/organization-model/domains/customers.ts +75 -0
  34. package/src/organization-model/domains/goals.ts +80 -0
  35. package/src/organization-model/domains/identity.ts +94 -0
  36. package/src/organization-model/domains/navigation.ts +43 -4
  37. package/src/organization-model/domains/offerings.ts +66 -0
  38. package/src/organization-model/domains/operations.ts +85 -0
  39. package/src/organization-model/domains/{delivery.ts → projects.ts} +6 -6
  40. package/src/organization-model/domains/{lead-gen.ts → prospecting.ts} +5 -5
  41. package/src/organization-model/domains/roles.ts +55 -0
  42. package/src/organization-model/domains/sales.ts +94 -0
  43. package/src/organization-model/domains/shared.ts +30 -1
  44. package/src/organization-model/domains/statuses.ts +130 -0
  45. package/src/organization-model/index.ts +3 -3
  46. package/src/organization-model/organization-graph.mdx +1 -0
  47. package/src/organization-model/organization-model.mdx +84 -19
  48. package/src/organization-model/published.ts +53 -8
  49. package/src/organization-model/schema.ts +67 -7
  50. package/src/organization-model/types.ts +31 -7
  51. package/src/platform/constants/versions.ts +1 -1
  52. package/src/platform/registry/types.ts +1 -1
  53. package/src/projects/api-schemas.ts +1 -0
  54. package/src/reference/_generated/contracts.md +116 -8
  55. package/src/reference/glossary.md +25 -4
  56. package/src/requests/__tests__/api-schemas.test.ts +277 -0
  57. package/src/requests/api-schemas.ts +83 -0
  58. package/src/requests/index.ts +1 -0
  59. package/src/scaffold-registry/__tests__/schema.test.ts +280 -0
  60. package/src/scaffold-registry/index.ts +194 -0
  61. package/src/scaffold-registry/schema.ts +144 -0
  62. package/src/supabase/database.types.ts +158 -6
  63. package/src/organization-model/domains/crm.ts +0 -46
  64. /package/src/business/{delivery → projects}/index.ts +0 -0
  65. /package/src/business/{delivery → projects}/types.ts +0 -0
  66. /package/src/business/{crm → sales}/api-schemas.ts +0 -0
@@ -0,0 +1,419 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ OfferingsDomainSchema,
4
+ ProductSchema,
5
+ PricingModelSchema,
6
+ DEFAULT_ORGANIZATION_MODEL_OFFERINGS
7
+ } from '../../domains/offerings'
8
+ import { resolveOrganizationModel } from '../../resolve'
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Group 1: ProductSchema — positive parse
12
+ // ---------------------------------------------------------------------------
13
+
14
+ describe('ProductSchema — positive parse', () => {
15
+ it('accepts a fully-populated product', () => {
16
+ const result = ProductSchema.safeParse({
17
+ id: 'product-starter-plan',
18
+ name: 'Starter Plan',
19
+ description: 'Full-service automation for small teams.',
20
+ pricingModel: 'subscription',
21
+ price: 299,
22
+ currency: 'USD',
23
+ targetSegmentIds: ['seg-smb'],
24
+ deliveryFeatureId: 'crm'
25
+ })
26
+ expect(result.success).toBe(true)
27
+ })
28
+
29
+ it('accepts a minimal product — only id required', () => {
30
+ const result = ProductSchema.safeParse({ id: 'product-minimal' })
31
+ expect(result.success).toBe(true)
32
+ if (result.success) {
33
+ expect(result.data.id).toBe('product-minimal')
34
+ expect(result.data.name).toBe('')
35
+ expect(result.data.description).toBe('')
36
+ expect(result.data.pricingModel).toBe('custom')
37
+ expect(result.data.price).toBe(0)
38
+ expect(result.data.currency).toBe('USD')
39
+ expect(result.data.targetSegmentIds).toEqual([])
40
+ expect(result.data.deliveryFeatureId).toBeUndefined()
41
+ }
42
+ })
43
+
44
+ it('trims whitespace from id and name', () => {
45
+ const result = ProductSchema.safeParse({
46
+ id: ' product-trim ',
47
+ name: ' Trimmed Name '
48
+ })
49
+ expect(result.success).toBe(true)
50
+ if (result.success) {
51
+ expect(result.data.id).toBe('product-trim')
52
+ expect(result.data.name).toBe('Trimmed Name')
53
+ }
54
+ })
55
+
56
+ it('accepts price of 0 (free tier)', () => {
57
+ const result = ProductSchema.safeParse({
58
+ id: 'product-free',
59
+ pricingModel: 'subscription',
60
+ price: 0
61
+ })
62
+ expect(result.success).toBe(true)
63
+ if (result.success) {
64
+ expect(result.data.price).toBe(0)
65
+ }
66
+ })
67
+
68
+ it('accepts all four pricing model values', () => {
69
+ const models = ['one-time', 'subscription', 'usage-based', 'custom'] as const
70
+ for (const pricingModel of models) {
71
+ const result = ProductSchema.safeParse({ id: `product-${pricingModel}`, pricingModel })
72
+ expect(result.success).toBe(true)
73
+ }
74
+ })
75
+ })
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Group 2: ProductSchema — default values
79
+ // ---------------------------------------------------------------------------
80
+
81
+ describe('ProductSchema — default values', () => {
82
+ it('pricingModel defaults to "custom"', () => {
83
+ const result = ProductSchema.safeParse({ id: 'product-defaults' })
84
+ expect(result.success).toBe(true)
85
+ if (result.success) {
86
+ expect(result.data.pricingModel).toBe('custom')
87
+ }
88
+ })
89
+
90
+ it('price defaults to 0', () => {
91
+ const result = ProductSchema.safeParse({ id: 'product-defaults' })
92
+ expect(result.success).toBe(true)
93
+ if (result.success) {
94
+ expect(result.data.price).toBe(0)
95
+ }
96
+ })
97
+
98
+ it('currency defaults to "USD"', () => {
99
+ const result = ProductSchema.safeParse({ id: 'product-defaults' })
100
+ expect(result.success).toBe(true)
101
+ if (result.success) {
102
+ expect(result.data.currency).toBe('USD')
103
+ }
104
+ })
105
+
106
+ it('targetSegmentIds defaults to empty array', () => {
107
+ const result = ProductSchema.safeParse({ id: 'product-defaults' })
108
+ expect(result.success).toBe(true)
109
+ if (result.success) {
110
+ expect(result.data.targetSegmentIds).toEqual([])
111
+ }
112
+ })
113
+
114
+ it('deliveryFeatureId defaults to undefined (field is optional)', () => {
115
+ const result = ProductSchema.safeParse({ id: 'product-defaults' })
116
+ expect(result.success).toBe(true)
117
+ if (result.success) {
118
+ expect(result.data.deliveryFeatureId).toBeUndefined()
119
+ }
120
+ })
121
+ })
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Group 3: ProductSchema — negative parse (wrong types / constraints)
125
+ // ---------------------------------------------------------------------------
126
+
127
+ describe('ProductSchema — negative parse', () => {
128
+ it('rejects missing id', () => {
129
+ const result = ProductSchema.safeParse({ name: 'No ID Product' })
130
+ expect(result.success).toBe(false)
131
+ })
132
+
133
+ it('rejects empty string id', () => {
134
+ const result = ProductSchema.safeParse({ id: '' })
135
+ expect(result.success).toBe(false)
136
+ })
137
+
138
+ it('rejects whitespace-only id (trims to empty string)', () => {
139
+ const result = ProductSchema.safeParse({ id: ' ' })
140
+ expect(result.success).toBe(false)
141
+ })
142
+
143
+ it('rejects an unknown pricingModel value', () => {
144
+ const result = ProductSchema.safeParse({ id: 'product-bad-model', pricingModel: 'freemium' })
145
+ expect(result.success).toBe(false)
146
+ })
147
+
148
+ it('rejects price below 0', () => {
149
+ const result = ProductSchema.safeParse({ id: 'product-negative-price', price: -1 })
150
+ expect(result.success).toBe(false)
151
+ })
152
+
153
+ it('rejects price as a string', () => {
154
+ const result = ProductSchema.safeParse({ id: 'product-string-price', price: '299' })
155
+ expect(result.success).toBe(false)
156
+ })
157
+
158
+ it('rejects targetSegmentIds as a non-array value', () => {
159
+ const result = ProductSchema.safeParse({ id: 'product-bad-segments', targetSegmentIds: 'seg-a' })
160
+ expect(result.success).toBe(false)
161
+ })
162
+ })
163
+
164
+ // ---------------------------------------------------------------------------
165
+ // Group 4: PricingModelSchema — enum coverage
166
+ // ---------------------------------------------------------------------------
167
+
168
+ describe('PricingModelSchema — enum', () => {
169
+ it('has exactly four values', () => {
170
+ const result = PricingModelSchema.safeParse('one-time')
171
+ expect(result.success).toBe(true)
172
+ // Verify the full enum options list matches expected set
173
+ expect(PricingModelSchema.options).toEqual(['one-time', 'subscription', 'usage-based', 'custom'])
174
+ })
175
+
176
+ it('rejects an empty string', () => {
177
+ const result = PricingModelSchema.safeParse('')
178
+ expect(result.success).toBe(false)
179
+ })
180
+
181
+ it('rejects a numeric value', () => {
182
+ const result = PricingModelSchema.safeParse(42)
183
+ expect(result.success).toBe(false)
184
+ })
185
+ })
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // Group 5: OfferingsDomainSchema — structural
189
+ // ---------------------------------------------------------------------------
190
+
191
+ describe('OfferingsDomainSchema — structural', () => {
192
+ it('accepts an empty products array', () => {
193
+ const result = OfferingsDomainSchema.safeParse({ products: [] })
194
+ expect(result.success).toBe(true)
195
+ })
196
+
197
+ it('defaults products to empty array when key is omitted', () => {
198
+ const result = OfferingsDomainSchema.safeParse({})
199
+ expect(result.success).toBe(true)
200
+ if (result.success) {
201
+ expect(result.data.products).toEqual([])
202
+ }
203
+ })
204
+
205
+ it('accepts multiple valid products', () => {
206
+ const result = OfferingsDomainSchema.safeParse({
207
+ products: [
208
+ { id: 'product-a', name: 'Product A', pricingModel: 'subscription' },
209
+ { id: 'product-b', name: 'Product B', pricingModel: 'one-time', price: 999 }
210
+ ]
211
+ })
212
+ expect(result.success).toBe(true)
213
+ if (result.success) {
214
+ expect(result.data.products).toHaveLength(2)
215
+ }
216
+ })
217
+
218
+ it('rejects products as a non-array value', () => {
219
+ const result = OfferingsDomainSchema.safeParse({ products: 'not an array' })
220
+ expect(result.success).toBe(false)
221
+ })
222
+
223
+ it('DEFAULT_ORGANIZATION_MODEL_OFFERINGS constant matches schema parse of empty object', () => {
224
+ const result = OfferingsDomainSchema.safeParse({})
225
+ expect(result.success).toBe(true)
226
+ if (result.success) {
227
+ expect(result.data).toEqual(DEFAULT_ORGANIZATION_MODEL_OFFERINGS)
228
+ }
229
+ })
230
+ })
231
+
232
+ // ---------------------------------------------------------------------------
233
+ // Group 6: Cross-ref validation via resolveOrganizationModel
234
+ // ---------------------------------------------------------------------------
235
+
236
+ describe('resolveOrganizationModel — offerings cross-ref (targetSegmentIds)', () => {
237
+ it('passes when targetSegmentIds references a declared customer segment', () => {
238
+ expect(() =>
239
+ resolveOrganizationModel({
240
+ customers: {
241
+ segments: [{ id: 'seg-agencies', name: 'SMB Agencies' }]
242
+ },
243
+ offerings: {
244
+ products: [
245
+ {
246
+ id: 'product-starter',
247
+ name: 'Starter Plan',
248
+ pricingModel: 'subscription',
249
+ targetSegmentIds: ['seg-agencies']
250
+ }
251
+ ]
252
+ }
253
+ })
254
+ ).not.toThrow()
255
+ })
256
+
257
+ it('throws when targetSegmentIds references a non-existent segment', () => {
258
+ expect(() =>
259
+ resolveOrganizationModel({
260
+ customers: { segments: [] },
261
+ offerings: {
262
+ products: [
263
+ {
264
+ id: 'product-bad-ref',
265
+ targetSegmentIds: ['nonexistent-segment']
266
+ }
267
+ ]
268
+ }
269
+ })
270
+ ).toThrow()
271
+ })
272
+
273
+ it('throws with a message referencing the unknown segment id', () => {
274
+ let errorMessage = ''
275
+ try {
276
+ resolveOrganizationModel({
277
+ customers: { segments: [] },
278
+ offerings: {
279
+ products: [
280
+ {
281
+ id: 'product-path-check',
282
+ targetSegmentIds: ['ghost-segment']
283
+ }
284
+ ]
285
+ }
286
+ })
287
+ } catch (e) {
288
+ errorMessage = String(e)
289
+ }
290
+ expect(errorMessage).toContain('ghost-segment')
291
+ })
292
+
293
+ it('passes when targetSegmentIds is empty (no refs required)', () => {
294
+ expect(() =>
295
+ resolveOrganizationModel({
296
+ offerings: {
297
+ products: [{ id: 'product-no-targets', targetSegmentIds: [] }]
298
+ }
299
+ })
300
+ ).not.toThrow()
301
+ })
302
+
303
+ it('passes when multiple products each reference a valid segment', () => {
304
+ expect(() =>
305
+ resolveOrganizationModel({
306
+ customers: {
307
+ segments: [
308
+ { id: 'seg-a', name: 'Segment A' },
309
+ { id: 'seg-b', name: 'Segment B' }
310
+ ]
311
+ },
312
+ offerings: {
313
+ products: [
314
+ { id: 'product-1', targetSegmentIds: ['seg-a'] },
315
+ { id: 'product-2', targetSegmentIds: ['seg-a', 'seg-b'] }
316
+ ]
317
+ }
318
+ })
319
+ ).not.toThrow()
320
+ })
321
+ })
322
+
323
+ // ---------------------------------------------------------------------------
324
+ // Group 7: Cross-ref validation — deliveryFeatureId
325
+ // ---------------------------------------------------------------------------
326
+
327
+ describe('resolveOrganizationModel — offerings cross-ref (deliveryFeatureId)', () => {
328
+ it('passes when deliveryFeatureId references a declared feature', () => {
329
+ // 'crm' is in DEFAULT_ORGANIZATION_MODEL.features
330
+ expect(() =>
331
+ resolveOrganizationModel({
332
+ offerings: {
333
+ products: [
334
+ {
335
+ id: 'product-with-delivery',
336
+ deliveryFeatureId: 'crm'
337
+ }
338
+ ]
339
+ }
340
+ })
341
+ ).not.toThrow()
342
+ })
343
+
344
+ it('throws when deliveryFeatureId references a non-existent feature', () => {
345
+ expect(() =>
346
+ resolveOrganizationModel({
347
+ offerings: {
348
+ products: [
349
+ {
350
+ id: 'product-bad-delivery',
351
+ deliveryFeatureId: 'nonexistent-feature'
352
+ }
353
+ ]
354
+ }
355
+ })
356
+ ).toThrow()
357
+ })
358
+
359
+ it('throws with a message referencing the unknown feature id', () => {
360
+ let errorMessage = ''
361
+ try {
362
+ resolveOrganizationModel({
363
+ offerings: {
364
+ products: [
365
+ {
366
+ id: 'product-delivery-path',
367
+ deliveryFeatureId: 'ghost-feature'
368
+ }
369
+ ]
370
+ }
371
+ })
372
+ } catch (e) {
373
+ errorMessage = String(e)
374
+ }
375
+ expect(errorMessage).toContain('ghost-feature')
376
+ })
377
+
378
+ it('passes when deliveryFeatureId is absent (field is optional)', () => {
379
+ expect(() =>
380
+ resolveOrganizationModel({
381
+ offerings: {
382
+ products: [{ id: 'product-no-delivery' }]
383
+ }
384
+ })
385
+ ).not.toThrow()
386
+ })
387
+ })
388
+
389
+ // ---------------------------------------------------------------------------
390
+ // Group 8: Integration — resolveOrganizationModel general
391
+ // ---------------------------------------------------------------------------
392
+
393
+ describe('resolveOrganizationModel — offerings domain integration', () => {
394
+ it('merges partial offerings override and preserves empty products default', () => {
395
+ const model = resolveOrganizationModel({ offerings: { products: [] } })
396
+ expect(model.offerings.products).toEqual([])
397
+ })
398
+
399
+ it('omitting offerings key entirely resolves to default empty products', () => {
400
+ const model = resolveOrganizationModel({})
401
+ expect(model.offerings).toBeDefined()
402
+ expect(model.offerings.products).toEqual([])
403
+ })
404
+
405
+ it('does not bleed offerings changes into other top-level domains', () => {
406
+ const model = resolveOrganizationModel({
407
+ customers: {
408
+ segments: [{ id: 'seg-isolation' }]
409
+ },
410
+ offerings: {
411
+ products: [{ id: 'product-isolation', targetSegmentIds: ['seg-isolation'] }]
412
+ }
413
+ })
414
+ expect(model.identity).toBeDefined()
415
+ expect(model.customers).toBeDefined()
416
+ expect(model.features).toBeDefined()
417
+ expect(model.navigation).toBeDefined()
418
+ })
419
+ })
@@ -0,0 +1,203 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ DEFAULT_ORGANIZATION_MODEL_OPERATIONS,
4
+ OperationEntrySchema,
5
+ OperationsDomainSchema,
6
+ OperationSemanticClassSchema
7
+ } from '../../domains/operations'
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Group 1: OperationEntrySchema — positive parse
11
+ // ---------------------------------------------------------------------------
12
+
13
+ describe('OperationEntrySchema — positive parse', () => {
14
+ it('accepts a fully-specified entry (id, label, semanticClass, featureId, supportedStatusSemanticClass)', () => {
15
+ const result = OperationEntrySchema.safeParse({
16
+ id: 'operations.queue',
17
+ label: 'HITL Queue',
18
+ semanticClass: 'queue',
19
+ featureId: 'operations',
20
+ supportedStatusSemanticClass: ['queue']
21
+ })
22
+ expect(result.success).toBe(true)
23
+ })
24
+
25
+ it('accepts a minimal entry without optional fields', () => {
26
+ const result = OperationEntrySchema.safeParse({
27
+ id: 'operations.sessions',
28
+ label: 'Sessions',
29
+ semanticClass: 'sessions'
30
+ })
31
+ expect(result.success).toBe(true)
32
+ if (result.success) {
33
+ expect(result.data.featureId).toBeUndefined()
34
+ expect(result.data.supportedStatusSemanticClass).toBeUndefined()
35
+ }
36
+ })
37
+
38
+ it('trims whitespace from id and label', () => {
39
+ const result = OperationEntrySchema.safeParse({
40
+ id: ' trimmed-id ',
41
+ label: ' Trimmed Label ',
42
+ semanticClass: 'executions'
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: OperationEntrySchema — negative parse
54
+ // ---------------------------------------------------------------------------
55
+
56
+ describe('OperationEntrySchema — negative parse', () => {
57
+ it('rejects a missing id field', () => {
58
+ const result = OperationEntrySchema.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 = OperationEntrySchema.safeParse({
67
+ id: 'operations.queue',
68
+ semanticClass: 'queue'
69
+ })
70
+ expect(result.success).toBe(false)
71
+ })
72
+
73
+ it('rejects a missing semanticClass field', () => {
74
+ const result = OperationEntrySchema.safeParse({
75
+ id: 'operations.queue',
76
+ label: 'HITL Queue'
77
+ })
78
+ expect(result.success).toBe(false)
79
+ })
80
+
81
+ it('rejects an unknown semanticClass value', () => {
82
+ const result = OperationEntrySchema.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 = OperationEntrySchema.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 = OperationEntrySchema.safeParse({
101
+ id: 'some.entry',
102
+ label: ' ',
103
+ semanticClass: 'queue'
104
+ })
105
+ expect(result.success).toBe(false)
106
+ })
107
+ })
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Group 3: OperationsDomainSchema — structural tests
111
+ // ---------------------------------------------------------------------------
112
+
113
+ describe('OperationsDomainSchema — structural tests', () => {
114
+ it('accepts an entries-empty domain object', () => {
115
+ const result = OperationsDomainSchema.safeParse({ entries: [] })
116
+ expect(result.success).toBe(true)
117
+ })
118
+
119
+ it('applies an empty-array default when entries is omitted', () => {
120
+ const result = OperationsDomainSchema.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 5 values declared
130
+ // ---------------------------------------------------------------------------
131
+
132
+ describe('OperationSemanticClassSchema — enum coverage', () => {
133
+ const expectedClasses = ['queue', 'executions', 'sessions', 'notifications', 'schedules'] as const
134
+
135
+ it('enum exposes exactly 5 semantic classes', () => {
136
+ expect(OperationSemanticClassSchema.options).toHaveLength(5)
137
+ })
138
+
139
+ it.each(expectedClasses)('"%s" is a valid semanticClass value', (cls) => {
140
+ const result = OperationSemanticClassSchema.safeParse(cls)
141
+ expect(result.success).toBe(true)
142
+ })
143
+
144
+ it('every entry in the seed uses one of the 5 known semanticClass values', () => {
145
+ const valid = new Set<string>(OperationSemanticClassSchema.options)
146
+ for (const entry of DEFAULT_ORGANIZATION_MODEL_OPERATIONS.entries) {
147
+ expect(
148
+ valid.has(entry.semanticClass),
149
+ `Unexpected semanticClass "${entry.semanticClass}" on entry "${entry.id}"`
150
+ ).toBe(true)
151
+ }
152
+ })
153
+
154
+ it('all 5 semanticClass values have exactly one seed entry', () => {
155
+ const presentClasses = new Set(DEFAULT_ORGANIZATION_MODEL_OPERATIONS.entries.map((e) => e.semanticClass))
156
+ for (const cls of OperationSemanticClassSchema.options) {
157
+ expect(presentClasses.has(cls), `No seed entry found for semanticClass "${cls}"`).toBe(true)
158
+ }
159
+ })
160
+ })
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Group 5: Seed completeness
164
+ // ---------------------------------------------------------------------------
165
+
166
+ describe('DEFAULT_ORGANIZATION_MODEL_OPERATIONS seed — completeness', () => {
167
+ it('has exactly 5 entries (one per entity category)', () => {
168
+ expect(DEFAULT_ORGANIZATION_MODEL_OPERATIONS.entries).toHaveLength(5)
169
+ })
170
+
171
+ it('all entry ids are unique', () => {
172
+ const ids = DEFAULT_ORGANIZATION_MODEL_OPERATIONS.entries.map((e) => e.id)
173
+ const uniqueIds = new Set(ids)
174
+ expect(uniqueIds.size).toBe(ids.length)
175
+ })
176
+
177
+ it('every entry has a non-empty id, label, and semanticClass', () => {
178
+ for (const entry of DEFAULT_ORGANIZATION_MODEL_OPERATIONS.entries) {
179
+ expect(entry.id.length).toBeGreaterThan(0)
180
+ expect(entry.label.length).toBeGreaterThan(0)
181
+ expect(entry.semanticClass.length).toBeGreaterThan(0)
182
+ }
183
+ })
184
+
185
+ it('queue entry references queue status semantic class', () => {
186
+ const queueEntry = DEFAULT_ORGANIZATION_MODEL_OPERATIONS.entries.find((e) => e.semanticClass === 'queue')
187
+ expect(queueEntry).toBeDefined()
188
+ expect(queueEntry?.supportedStatusSemanticClass).toContain('queue')
189
+ })
190
+
191
+ it('executions entry references execution status semantic class', () => {
192
+ const execEntry = DEFAULT_ORGANIZATION_MODEL_OPERATIONS.entries.find((e) => e.semanticClass === 'executions')
193
+ expect(execEntry).toBeDefined()
194
+ expect(execEntry?.supportedStatusSemanticClass).toContain('execution')
195
+ })
196
+
197
+ it('schedules entry references schedule and schedule.run status semantic classes', () => {
198
+ const schedEntry = DEFAULT_ORGANIZATION_MODEL_OPERATIONS.entries.find((e) => e.semanticClass === 'schedules')
199
+ expect(schedEntry).toBeDefined()
200
+ expect(schedEntry?.supportedStatusSemanticClass).toContain('schedule')
201
+ expect(schedEntry?.supportedStatusSemanticClass).toContain('schedule.run')
202
+ })
203
+ })