@elevasis/core 0.21.0 → 0.23.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 (132) hide show
  1. package/dist/index.d.ts +2518 -2169
  2. package/dist/index.js +2495 -1095
  3. package/dist/knowledge/index.d.ts +706 -1044
  4. package/dist/knowledge/index.js +9 -9
  5. package/dist/organization-model/index.d.ts +2518 -2169
  6. package/dist/organization-model/index.js +2495 -1095
  7. package/dist/test-utils/index.d.ts +826 -1014
  8. package/dist/test-utils/index.js +1894 -1032
  9. package/package.json +3 -3
  10. package/src/__tests__/template-core-compatibility.test.ts +11 -79
  11. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +852 -397
  12. package/src/auth/multi-tenancy/permissions.ts +20 -8
  13. package/src/business/README.md +2 -2
  14. package/src/business/acquisition/api-schemas.test.ts +175 -2
  15. package/src/business/acquisition/api-schemas.ts +132 -16
  16. package/src/business/acquisition/build-templates.test.ts +4 -4
  17. package/src/business/acquisition/build-templates.ts +72 -30
  18. package/src/business/acquisition/crm-state-actions.test.ts +13 -11
  19. package/src/business/acquisition/index.ts +12 -0
  20. package/src/business/acquisition/types.ts +7 -3
  21. package/src/business/clients/api-schemas.test.ts +115 -0
  22. package/src/business/clients/api-schemas.ts +158 -0
  23. package/src/business/clients/index.ts +1 -0
  24. package/src/business/deals/api-schemas.ts +8 -0
  25. package/src/business/index.ts +5 -2
  26. package/src/business/projects/types.ts +19 -0
  27. package/src/execution/engine/__tests__/fixtures/test-agents.ts +10 -8
  28. package/src/execution/engine/agent/core/__tests__/agent.test.ts +16 -12
  29. package/src/execution/engine/agent/core/__tests__/error-passthrough.test.ts +4 -3
  30. package/src/execution/engine/agent/core/types.ts +25 -15
  31. package/src/execution/engine/agent/index.ts +6 -4
  32. package/src/execution/engine/agent/reasoning/__tests__/request-builder.test.ts +24 -18
  33. package/src/execution/engine/index.ts +3 -0
  34. package/src/execution/engine/workflow/types.ts +9 -2
  35. package/src/knowledge/README.md +8 -7
  36. package/src/knowledge/__tests__/queries.test.ts +74 -73
  37. package/src/knowledge/format.ts +10 -9
  38. package/src/knowledge/index.ts +1 -1
  39. package/src/knowledge/published.ts +1 -1
  40. package/src/knowledge/queries.ts +26 -25
  41. package/src/organization-model/README.md +73 -26
  42. package/src/organization-model/__tests__/content-kinds-registry.test.ts +210 -0
  43. package/src/organization-model/__tests__/defaults.test.ts +76 -96
  44. package/src/organization-model/__tests__/domains/actions.test.ts +56 -0
  45. package/src/organization-model/__tests__/domains/customers.test.ts +299 -295
  46. package/src/organization-model/__tests__/domains/entities.test.ts +56 -0
  47. package/src/organization-model/__tests__/domains/goals.test.ts +493 -479
  48. package/src/organization-model/__tests__/domains/identity.test.ts +280 -279
  49. package/src/organization-model/__tests__/domains/navigation.test.ts +268 -212
  50. package/src/organization-model/__tests__/domains/offerings.test.ts +414 -419
  51. package/src/organization-model/__tests__/domains/policies.test.ts +323 -0
  52. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +271 -271
  53. package/src/organization-model/__tests__/domains/resources.test.ts +310 -0
  54. package/src/organization-model/__tests__/domains/roles.test.ts +463 -347
  55. package/src/organization-model/__tests__/domains/statuses.test.ts +246 -243
  56. package/src/organization-model/__tests__/domains/systems.test.ts +209 -0
  57. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +361 -0
  58. package/src/organization-model/__tests__/foundation.test.ts +74 -102
  59. package/src/organization-model/__tests__/get-resources-for-system.test.ts +144 -0
  60. package/src/organization-model/__tests__/graph.test.ts +899 -71
  61. package/src/organization-model/__tests__/knowledge.test.ts +209 -49
  62. package/src/organization-model/__tests__/lookup-helpers.test.ts +438 -0
  63. package/src/organization-model/__tests__/migration-helpers.test.ts +591 -0
  64. package/src/organization-model/__tests__/prospecting-ssot.test.ts +36 -27
  65. package/src/organization-model/__tests__/recursive-system-schema.test.ts +520 -0
  66. package/src/organization-model/__tests__/resolve.test.ts +174 -23
  67. package/src/organization-model/__tests__/schema.test.ts +291 -114
  68. package/src/organization-model/__tests__/surface-projection.test.ts +207 -97
  69. package/src/organization-model/catalogs/lead-gen.ts +144 -0
  70. package/src/organization-model/content-kinds/config.ts +36 -0
  71. package/src/organization-model/content-kinds/index.ts +74 -0
  72. package/src/organization-model/content-kinds/pipeline.ts +68 -0
  73. package/src/organization-model/content-kinds/registry.ts +44 -0
  74. package/src/organization-model/content-kinds/status.ts +71 -0
  75. package/src/organization-model/content-kinds/template.ts +83 -0
  76. package/src/organization-model/content-kinds/types.ts +117 -0
  77. package/src/organization-model/contracts.ts +13 -3
  78. package/src/organization-model/defaults.ts +499 -86
  79. package/src/organization-model/domains/actions.ts +239 -0
  80. package/src/organization-model/domains/customers.ts +78 -75
  81. package/src/organization-model/domains/entities.ts +144 -0
  82. package/src/organization-model/domains/goals.ts +83 -80
  83. package/src/organization-model/domains/knowledge.ts +76 -17
  84. package/src/organization-model/domains/navigation.ts +107 -384
  85. package/src/organization-model/domains/offerings.ts +71 -66
  86. package/src/organization-model/domains/policies.ts +102 -0
  87. package/src/organization-model/domains/projects.ts +14 -48
  88. package/src/organization-model/domains/prospecting.ts +62 -181
  89. package/src/organization-model/domains/resources.ts +145 -0
  90. package/src/organization-model/domains/roles.ts +96 -55
  91. package/src/organization-model/domains/sales.ts +10 -219
  92. package/src/organization-model/domains/shared.ts +57 -57
  93. package/src/organization-model/domains/statuses.ts +339 -130
  94. package/src/organization-model/domains/systems.ts +203 -0
  95. package/src/organization-model/foundation.ts +54 -67
  96. package/src/organization-model/graph/build.ts +682 -54
  97. package/src/organization-model/graph/link.ts +1 -1
  98. package/src/organization-model/graph/schema.ts +24 -9
  99. package/src/organization-model/graph/types.ts +20 -7
  100. package/src/organization-model/helpers.ts +231 -26
  101. package/src/organization-model/icons.ts +1 -0
  102. package/src/organization-model/index.ts +118 -5
  103. package/src/organization-model/migration-helpers.ts +249 -0
  104. package/src/organization-model/organization-graph.mdx +16 -15
  105. package/src/organization-model/organization-model.mdx +111 -44
  106. package/src/organization-model/published.ts +172 -19
  107. package/src/organization-model/resolve.ts +117 -54
  108. package/src/organization-model/schema.ts +654 -112
  109. package/src/organization-model/surface-projection.ts +116 -122
  110. package/src/organization-model/types.ts +146 -20
  111. package/src/platform/api/types.ts +38 -35
  112. package/src/platform/constants/versions.ts +1 -1
  113. package/src/platform/registry/__tests__/command-view.test.ts +6 -8
  114. package/src/platform/registry/__tests__/resource-link.test.ts +13 -8
  115. package/src/platform/registry/__tests__/resource-registry.integration.test.ts +16 -31
  116. package/src/platform/registry/__tests__/resource-registry.nested-systems.test.ts +245 -0
  117. package/src/platform/registry/__tests__/resource-registry.test.ts +2053 -2005
  118. package/src/platform/registry/__tests__/validation.test.ts +1347 -1086
  119. package/src/platform/registry/index.ts +14 -0
  120. package/src/platform/registry/resource-registry.ts +52 -2
  121. package/src/platform/registry/serialization.ts +241 -202
  122. package/src/platform/registry/serialized-types.ts +1 -0
  123. package/src/platform/registry/types.ts +411 -361
  124. package/src/platform/registry/validation.ts +745 -513
  125. package/src/projects/api-schemas.ts +290 -267
  126. package/src/reference/_generated/contracts.md +853 -397
  127. package/src/reference/glossary.md +23 -18
  128. package/src/supabase/database.types.ts +181 -0
  129. package/src/test-utils/test-utils.test.ts +1 -6
  130. package/src/organization-model/__tests__/domains/operations.test.ts +0 -203
  131. package/src/organization-model/domains/features.ts +0 -31
  132. package/src/organization-model/domains/operations.ts +0 -85
@@ -1,279 +1,280 @@
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
- clientBrief: '',
261
- businessHours: {
262
- monday: { open: '09:00', close: '17:00' }
263
- }
264
- }
265
- }
266
- const model = resolveOrganizationModel(override)
267
- expect(model.identity).toEqual(override.identity)
268
- })
269
-
270
- it('does not bleed identity changes into other top-level domains', () => {
271
- const model = resolveOrganizationModel({
272
- identity: { mission: 'Isolated change.' }
273
- })
274
- // navigation, features, statuses etc. must still be present
275
- expect(model.navigation).toBeDefined()
276
- expect(model.features).toBeDefined()
277
- expect(model.statuses).toBeDefined()
278
- })
279
- })
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
+ clientBrief: '',
261
+ businessHours: {
262
+ monday: { open: '09:00', close: '17:00' }
263
+ }
264
+ }
265
+ }
266
+ const model = resolveOrganizationModel(override)
267
+ expect(model.identity).toEqual(override.identity)
268
+ })
269
+
270
+ it('does not bleed identity changes into other top-level domains', () => {
271
+ const model = resolveOrganizationModel({
272
+ identity: { mission: 'Isolated change.' }
273
+ })
274
+ // Phase 4 (D1, D8): model.navigation and model.statuses removed from top-level OM.
275
+ // Verify other sibling domains still present instead.
276
+ expect(model.systems).toBeDefined()
277
+ expect(model.knowledge).toBeDefined()
278
+ expect(model.navigation).toBeDefined()
279
+ })
280
+ })