@elevasis/core 0.22.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 (112) hide show
  1. package/dist/index.d.ts +2330 -2391
  2. package/dist/index.js +2322 -1147
  3. package/dist/knowledge/index.d.ts +702 -1136
  4. package/dist/knowledge/index.js +9 -9
  5. package/dist/organization-model/index.d.ts +2330 -2391
  6. package/dist/organization-model/index.js +2322 -1147
  7. package/dist/test-utils/index.d.ts +703 -1106
  8. package/dist/test-utils/index.js +1735 -1089
  9. package/package.json +1 -1
  10. package/src/__tests__/template-core-compatibility.test.ts +11 -79
  11. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +360 -98
  12. package/src/business/acquisition/api-schemas.test.ts +2 -2
  13. package/src/business/acquisition/api-schemas.ts +7 -9
  14. package/src/business/acquisition/build-templates.test.ts +4 -4
  15. package/src/business/acquisition/build-templates.ts +72 -30
  16. package/src/business/acquisition/crm-state-actions.test.ts +13 -11
  17. package/src/business/acquisition/types.ts +7 -3
  18. package/src/execution/engine/agent/core/types.ts +1 -1
  19. package/src/execution/engine/workflow/types.ts +2 -2
  20. package/src/knowledge/README.md +8 -7
  21. package/src/knowledge/__tests__/queries.test.ts +74 -73
  22. package/src/knowledge/format.ts +10 -9
  23. package/src/knowledge/index.ts +1 -1
  24. package/src/knowledge/published.ts +1 -1
  25. package/src/knowledge/queries.ts +26 -25
  26. package/src/organization-model/README.md +66 -26
  27. package/src/organization-model/__tests__/content-kinds-registry.test.ts +210 -0
  28. package/src/organization-model/__tests__/defaults.test.ts +72 -98
  29. package/src/organization-model/__tests__/domains/actions.test.ts +56 -0
  30. package/src/organization-model/__tests__/domains/customers.test.ts +299 -295
  31. package/src/organization-model/__tests__/domains/entities.test.ts +56 -0
  32. package/src/organization-model/__tests__/domains/goals.test.ts +493 -479
  33. package/src/organization-model/__tests__/domains/identity.test.ts +280 -279
  34. package/src/organization-model/__tests__/domains/navigation.test.ts +268 -212
  35. package/src/organization-model/__tests__/domains/offerings.test.ts +414 -419
  36. package/src/organization-model/__tests__/domains/policies.test.ts +323 -0
  37. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +271 -271
  38. package/src/organization-model/__tests__/domains/resources.test.ts +159 -37
  39. package/src/organization-model/__tests__/domains/roles.test.ts +147 -86
  40. package/src/organization-model/__tests__/domains/statuses.test.ts +246 -243
  41. package/src/organization-model/__tests__/domains/systems.test.ts +67 -51
  42. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +361 -0
  43. package/src/organization-model/__tests__/foundation.test.ts +74 -102
  44. package/src/organization-model/__tests__/get-resources-for-system.test.ts +144 -0
  45. package/src/organization-model/__tests__/graph.test.ts +899 -71
  46. package/src/organization-model/__tests__/knowledge.test.ts +173 -52
  47. package/src/organization-model/__tests__/lookup-helpers.test.ts +438 -0
  48. package/src/organization-model/__tests__/migration-helpers.test.ts +591 -0
  49. package/src/organization-model/__tests__/prospecting-ssot.test.ts +36 -27
  50. package/src/organization-model/__tests__/recursive-system-schema.test.ts +520 -0
  51. package/src/organization-model/__tests__/resolve.test.ts +174 -23
  52. package/src/organization-model/__tests__/schema.test.ts +291 -114
  53. package/src/organization-model/__tests__/surface-projection.test.ts +207 -97
  54. package/src/organization-model/catalogs/lead-gen.ts +144 -0
  55. package/src/organization-model/content-kinds/config.ts +36 -0
  56. package/src/organization-model/content-kinds/index.ts +74 -0
  57. package/src/organization-model/content-kinds/pipeline.ts +68 -0
  58. package/src/organization-model/content-kinds/registry.ts +44 -0
  59. package/src/organization-model/content-kinds/status.ts +71 -0
  60. package/src/organization-model/content-kinds/template.ts +83 -0
  61. package/src/organization-model/content-kinds/types.ts +117 -0
  62. package/src/organization-model/contracts.ts +13 -3
  63. package/src/organization-model/defaults.ts +488 -96
  64. package/src/organization-model/domains/actions.ts +239 -0
  65. package/src/organization-model/domains/customers.ts +78 -75
  66. package/src/organization-model/domains/entities.ts +144 -0
  67. package/src/organization-model/domains/goals.ts +83 -80
  68. package/src/organization-model/domains/knowledge.ts +74 -16
  69. package/src/organization-model/domains/navigation.ts +107 -384
  70. package/src/organization-model/domains/offerings.ts +71 -66
  71. package/src/organization-model/domains/policies.ts +102 -0
  72. package/src/organization-model/domains/projects.ts +14 -48
  73. package/src/organization-model/domains/prospecting.ts +62 -181
  74. package/src/organization-model/domains/resources.ts +81 -24
  75. package/src/organization-model/domains/roles.ts +13 -10
  76. package/src/organization-model/domains/sales.ts +10 -219
  77. package/src/organization-model/domains/shared.ts +57 -57
  78. package/src/organization-model/domains/statuses.ts +339 -130
  79. package/src/organization-model/domains/systems.ts +186 -29
  80. package/src/organization-model/foundation.ts +54 -67
  81. package/src/organization-model/graph/build.ts +682 -54
  82. package/src/organization-model/graph/link.ts +1 -1
  83. package/src/organization-model/graph/schema.ts +24 -9
  84. package/src/organization-model/graph/types.ts +20 -7
  85. package/src/organization-model/helpers.ts +231 -26
  86. package/src/organization-model/index.ts +116 -5
  87. package/src/organization-model/migration-helpers.ts +249 -0
  88. package/src/organization-model/organization-graph.mdx +16 -15
  89. package/src/organization-model/organization-model.mdx +89 -41
  90. package/src/organization-model/published.ts +120 -18
  91. package/src/organization-model/resolve.ts +117 -54
  92. package/src/organization-model/schema.ts +561 -140
  93. package/src/organization-model/surface-projection.ts +116 -122
  94. package/src/organization-model/types.ts +102 -21
  95. package/src/platform/constants/versions.ts +1 -1
  96. package/src/platform/registry/__tests__/command-view.test.ts +6 -8
  97. package/src/platform/registry/__tests__/resource-link.test.ts +13 -8
  98. package/src/platform/registry/__tests__/resource-registry.integration.test.ts +16 -31
  99. package/src/platform/registry/__tests__/resource-registry.nested-systems.test.ts +245 -0
  100. package/src/platform/registry/__tests__/resource-registry.test.ts +9 -7
  101. package/src/platform/registry/__tests__/validation.test.ts +15 -11
  102. package/src/platform/registry/resource-registry.ts +20 -8
  103. package/src/platform/registry/serialization.ts +7 -7
  104. package/src/platform/registry/types.ts +3 -3
  105. package/src/platform/registry/validation.ts +17 -15
  106. package/src/reference/_generated/contracts.md +362 -99
  107. package/src/reference/glossary.md +18 -18
  108. package/src/supabase/database.types.ts +60 -0
  109. package/src/test-utils/test-utils.test.ts +1 -6
  110. package/src/organization-model/__tests__/domains/operations.test.ts +0 -203
  111. package/src/organization-model/domains/features.ts +0 -31
  112. package/src/organization-model/domains/operations.ts +0 -85
@@ -13,6 +13,7 @@ describe('RoleSchema - positive parse', () => {
13
13
  it('accepts a fully-populated role', () => {
14
14
  const result = RoleSchema.safeParse({
15
15
  id: 'role-ceo',
16
+ order: 10,
16
17
  title: 'CEO',
17
18
  responsibilities: ['Set company direction', 'Hire and develop leadership team'],
18
19
  reportsToId: undefined,
@@ -23,8 +24,8 @@ describe('RoleSchema - positive parse', () => {
23
24
  expect(result.success).toBe(true)
24
25
  })
25
26
 
26
- it('accepts a minimal role - only id and title required', () => {
27
- const result = RoleSchema.safeParse({ id: 'role-minimal', title: 'Head of Sales' })
27
+ it('accepts a minimal role - only id, order, and title required', () => {
28
+ const result = RoleSchema.safeParse({ id: 'role-minimal', order: 10, title: 'Head of Sales' })
28
29
 
29
30
  expect(result.success).toBe(true)
30
31
  if (result.success) {
@@ -39,23 +40,33 @@ describe('RoleSchema - positive parse', () => {
39
40
 
40
41
  it('accepts human, agent, team, and multi-holder variants', () => {
41
42
  expect(
42
- RoleSchema.safeParse({ id: 'role-human', title: 'Human', heldBy: { kind: 'human', userId: 'user.alice' } })
43
- .success
43
+ RoleSchema.safeParse({
44
+ id: 'role-human',
45
+ order: 10,
46
+ title: 'Human',
47
+ heldBy: { kind: 'human', userId: 'user.alice' }
48
+ }).success
44
49
  ).toBe(true)
45
50
  expect(
46
51
  RoleSchema.safeParse({
47
52
  id: 'role-agent',
53
+ order: 10,
48
54
  title: 'Agent',
49
55
  heldBy: { kind: 'agent', agentId: 'command-center-assistant' }
50
56
  }).success
51
57
  ).toBe(true)
52
58
  expect(
53
- RoleSchema.safeParse({ id: 'role-team', title: 'Team', heldBy: { kind: 'team', memberIds: ['user.alice'] } })
54
- .success
59
+ RoleSchema.safeParse({
60
+ id: 'role-team',
61
+ order: 10,
62
+ title: 'Team',
63
+ heldBy: { kind: 'team', memberIds: ['user.alice'] }
64
+ }).success
55
65
  ).toBe(true)
56
66
  expect(
57
67
  RoleSchema.safeParse({
58
68
  id: 'role-multi',
69
+ order: 10,
59
70
  title: 'Multi',
60
71
  heldBy: [
61
72
  { kind: 'human', userId: 'user.alice' },
@@ -68,6 +79,7 @@ describe('RoleSchema - positive parse', () => {
68
79
  it('trims whitespace from id and title', () => {
69
80
  const result = RoleSchema.safeParse({
70
81
  id: ' role-trim ',
82
+ order: 10,
71
83
  title: ' Trimmed Title '
72
84
  })
73
85
 
@@ -81,7 +93,7 @@ describe('RoleSchema - positive parse', () => {
81
93
 
82
94
  describe('RoleSchema - default values', () => {
83
95
  it('responsibilities defaults to empty array when omitted', () => {
84
- const result = RoleSchema.safeParse({ id: 'role-defaults', title: 'Engineer' })
96
+ const result = RoleSchema.safeParse({ id: 'role-defaults', order: 10, title: 'Engineer' })
85
97
 
86
98
  expect(result.success).toBe(true)
87
99
  if (result.success) {
@@ -90,7 +102,7 @@ describe('RoleSchema - default values', () => {
90
102
  })
91
103
 
92
104
  it('reportsToId, heldBy, and responsibleFor are optional', () => {
93
- const result = RoleSchema.safeParse({ id: 'role-defaults', title: 'Engineer' })
105
+ const result = RoleSchema.safeParse({ id: 'role-defaults', order: 10, title: 'Engineer' })
94
106
 
95
107
  expect(result.success).toBe(true)
96
108
  if (result.success) {
@@ -104,16 +116,17 @@ describe('RoleSchema - default values', () => {
104
116
  describe('RoleSchema - negative parse', () => {
105
117
  it('rejects missing or invalid required fields', () => {
106
118
  expect(RoleSchema.safeParse({ title: 'No ID Role' }).success).toBe(false)
107
- expect(RoleSchema.safeParse({ id: '', title: 'Empty ID' }).success).toBe(false)
108
- expect(RoleSchema.safeParse({ id: ' ', title: 'Whitespace ID' }).success).toBe(false)
109
- expect(RoleSchema.safeParse({ id: 'role-no-title' }).success).toBe(false)
110
- expect(RoleSchema.safeParse({ id: 'role-empty-title', title: '' }).success).toBe(false)
111
- expect(RoleSchema.safeParse({ id: 'role-whitespace-title', title: ' ' }).success).toBe(false)
119
+ expect(RoleSchema.safeParse({ id: '', order: 10, title: 'Empty ID' }).success).toBe(false)
120
+ expect(RoleSchema.safeParse({ id: ' ', order: 10, title: 'Whitespace ID' }).success).toBe(false)
121
+ expect(RoleSchema.safeParse({ id: 'role-no-title', order: 10 }).success).toBe(false)
122
+ expect(RoleSchema.safeParse({ id: 'role-empty-title', order: 10, title: '' }).success).toBe(false)
123
+ expect(RoleSchema.safeParse({ id: 'role-whitespace-title', order: 10, title: ' ' }).success).toBe(false)
112
124
  })
113
125
 
114
126
  it('rejects responsibilities as a non-array value', () => {
115
127
  const result = RoleSchema.safeParse({
116
128
  id: 'role-bad-resp',
129
+ order: 10,
117
130
  title: 'Bad Role',
118
131
  responsibilities: 'single string'
119
132
  })
@@ -122,14 +135,20 @@ describe('RoleSchema - negative parse', () => {
122
135
  })
123
136
 
124
137
  it('rejects legacy string heldBy values and invalid holders', () => {
125
- expect(RoleSchema.safeParse({ id: 'role-legacy-holder', title: 'Legacy Holder', heldBy: 'Alice' }).success).toBe(
126
- false
127
- )
128
138
  expect(
129
- RoleSchema.safeParse({ id: 'role-empty-team', title: 'Empty Team', heldBy: { kind: 'team', memberIds: [] } })
130
- .success
139
+ RoleSchema.safeParse({ id: 'role-legacy-holder', order: 10, title: 'Legacy Holder', heldBy: 'Alice' }).success
140
+ ).toBe(false)
141
+ expect(
142
+ RoleSchema.safeParse({
143
+ id: 'role-empty-team',
144
+ order: 10,
145
+ title: 'Empty Team',
146
+ heldBy: { kind: 'team', memberIds: [] }
147
+ }).success
148
+ ).toBe(false)
149
+ expect(
150
+ RoleSchema.safeParse({ id: 'role-empty-holders', order: 10, title: 'Empty Holders', heldBy: [] }).success
131
151
  ).toBe(false)
132
- expect(RoleSchema.safeParse({ id: 'role-empty-holders', title: 'Empty Holders', heldBy: [] }).success).toBe(false)
133
152
  })
134
153
  })
135
154
 
@@ -149,6 +168,7 @@ describe('RoleSchema - plain-language field names (no EOS jargon)', () => {
149
168
  it('parsed role has the plain-language keys title / responsibilities / reportsToId / heldBy / responsibleFor', () => {
150
169
  const result = RoleSchema.safeParse({
151
170
  id: 'role-plain',
171
+ order: 10,
152
172
  title: 'Product Lead',
153
173
  responsibilities: ['Own roadmap'],
154
174
  reportsToId: 'role-ceo',
@@ -173,35 +193,37 @@ describe('RoleSchema - plain-language field names (no EOS jargon)', () => {
173
193
  })
174
194
 
175
195
  describe('RolesDomainSchema - structural', () => {
176
- it('accepts an empty roles array', () => {
177
- expect(RolesDomainSchema.safeParse({ roles: [] }).success).toBe(true)
196
+ it('accepts an empty roles record', () => {
197
+ expect(RolesDomainSchema.safeParse({}).success).toBe(true)
178
198
  })
179
199
 
180
- it('defaults roles to empty array when key is omitted', () => {
200
+ it('defaults roles to empty record when omitted', () => {
181
201
  const result = RolesDomainSchema.safeParse({})
182
202
 
183
203
  expect(result.success).toBe(true)
184
204
  if (result.success) {
185
- expect(result.data.roles).toEqual([])
205
+ expect(result.data).toEqual({})
186
206
  }
187
207
  })
188
208
 
189
209
  it('accepts multiple valid roles', () => {
190
210
  const result = RolesDomainSchema.safeParse({
191
- roles: [
192
- { id: 'role-a', title: 'CEO' },
193
- { id: 'role-b', title: 'COO', reportsToId: 'role-a' }
194
- ]
211
+ 'role-a': { id: 'role-a', order: 10, title: 'CEO' },
212
+ 'role-b': { id: 'role-b', order: 20, title: 'COO', reportsToId: 'role-a' }
195
213
  })
196
214
 
197
215
  expect(result.success).toBe(true)
198
216
  if (result.success) {
199
- expect(result.data.roles).toHaveLength(2)
217
+ expect(Object.keys(result.data)).toHaveLength(2)
200
218
  }
201
219
  })
202
220
 
203
- it('rejects roles as a non-array value', () => {
204
- expect(RolesDomainSchema.safeParse({ roles: 'not an array' }).success).toBe(false)
221
+ it('rejects a record where entry id does not match map key', () => {
222
+ expect(
223
+ RolesDomainSchema.safeParse({
224
+ 'role-a': { id: 'role-b', order: 10, title: 'Mismatched' }
225
+ }).success
226
+ ).toBe(false)
205
227
  })
206
228
 
207
229
  it('DEFAULT_ORGANIZATION_MODEL_ROLES constant matches schema parse of empty object', () => {
@@ -219,10 +241,8 @@ describe('resolveOrganizationModel - roles cross-ref (reportsToId)', () => {
219
241
  expect(() =>
220
242
  resolveOrganizationModel({
221
243
  roles: {
222
- roles: [
223
- { id: 'role-ceo', title: 'CEO' },
224
- { id: 'role-coo', title: 'COO', reportsToId: 'role-ceo' }
225
- ]
244
+ 'role-ceo': { id: 'role-ceo', order: 10, title: 'CEO' },
245
+ 'role-coo': { id: 'role-coo', order: 20, title: 'COO', reportsToId: 'role-ceo' }
226
246
  }
227
247
  })
228
248
  ).not.toThrow()
@@ -232,7 +252,7 @@ describe('resolveOrganizationModel - roles cross-ref (reportsToId)', () => {
232
252
  expect(() =>
233
253
  resolveOrganizationModel({
234
254
  roles: {
235
- roles: [{ id: 'role-orphan', title: 'Orphan Role', reportsToId: 'nonexistent-role' }]
255
+ 'role-orphan': { id: 'role-orphan', order: 10, title: 'Orphan Role', reportsToId: 'nonexistent-role' }
236
256
  }
237
257
  })
238
258
  ).toThrow()
@@ -242,27 +262,50 @@ describe('resolveOrganizationModel - roles cross-ref (reportsToId)', () => {
242
262
  expect(() =>
243
263
  resolveOrganizationModel({
244
264
  roles: {
245
- roles: [{ id: 'role-bad-ref', title: 'Bad Ref Role', reportsToId: 'ghost-role' }]
265
+ 'role-bad-ref': { id: 'role-bad-ref', order: 10, title: 'Bad Ref Role', reportsToId: 'ghost-role' }
246
266
  }
247
267
  })
248
268
  ).toThrow(/ghost-role/)
249
269
  })
250
270
 
251
- it('passes when reportsToId is absent or roles array is empty', () => {
271
+ it('passes when reportsToId is absent or roles record is empty', () => {
252
272
  expect(() =>
253
273
  resolveOrganizationModel({
254
274
  roles: {
255
- roles: [{ id: 'role-top', title: 'Top Level Role' }]
275
+ 'role-top': { id: 'role-top', order: 10, title: 'Top Level Role' }
256
276
  }
257
277
  })
258
278
  ).not.toThrow()
259
- expect(() => resolveOrganizationModel({ roles: { roles: [] } })).not.toThrow()
279
+ expect(() => resolveOrganizationModel({ roles: {} })).not.toThrow()
280
+ })
281
+
282
+ it('throws when reportsToId points to the same role', () => {
283
+ expect(() =>
284
+ resolveOrganizationModel({
285
+ roles: {
286
+ 'role-loop': { id: 'role-loop', order: 10, title: 'Loop Role', reportsToId: 'role-loop' }
287
+ }
288
+ })
289
+ ).toThrow(/reportsToId cycle/)
290
+ })
291
+
292
+ it('throws when reportsToId creates an indirect cycle', () => {
293
+ expect(() =>
294
+ resolveOrganizationModel({
295
+ roles: {
296
+ 'role-a': { id: 'role-a', order: 10, title: 'Role A', reportsToId: 'role-c' },
297
+ 'role-b': { id: 'role-b', order: 20, title: 'Role B', reportsToId: 'role-a' },
298
+ 'role-c': { id: 'role-c', order: 30, title: 'Role C', reportsToId: 'role-b' }
299
+ }
300
+ })
301
+ ).toThrow(/reportsToId cycle/)
260
302
  })
261
303
  })
262
304
 
263
305
  describe('resolveOrganizationModel - roles cross-ref (responsibleFor and heldBy)', () => {
264
306
  const validSystem = {
265
307
  id: 'sys.platform',
308
+ order: 10,
266
309
  title: 'Platform',
267
310
  description: 'Owns platform operations.',
268
311
  kind: 'platform' as const,
@@ -271,19 +314,25 @@ describe('resolveOrganizationModel - roles cross-ref (responsibleFor and heldBy)
271
314
 
272
315
  const validAgentResource = {
273
316
  id: 'command-center-assistant',
317
+ order: 10,
274
318
  kind: 'agent' as const,
275
- systemId: 'sys.platform',
319
+ systemPath: 'sys.platform',
276
320
  status: 'active' as const,
277
- agentKind: 'system' as const,
321
+ agentKind: 'platform' as const,
278
322
  sessionCapable: true
279
323
  }
280
324
 
281
325
  it('passes when responsibleFor references a declared system id', () => {
282
326
  expect(() =>
283
327
  resolveOrganizationModel({
284
- systems: { systems: [validSystem] },
328
+ systems: { 'sys.platform': validSystem },
285
329
  roles: {
286
- roles: [{ id: 'role.platform-owner', title: 'Platform Owner', responsibleFor: ['sys.platform'] }]
330
+ 'role.platform-owner': {
331
+ id: 'role.platform-owner',
332
+ order: 10,
333
+ title: 'Platform Owner',
334
+ responsibleFor: ['sys.platform']
335
+ }
287
336
  }
288
337
  })
289
338
  ).not.toThrow()
@@ -293,7 +342,12 @@ describe('resolveOrganizationModel - roles cross-ref (responsibleFor and heldBy)
293
342
  expect(() =>
294
343
  resolveOrganizationModel({
295
344
  roles: {
296
- roles: [{ id: 'role.platform-owner', title: 'Platform Owner', responsibleFor: ['sys.missing'] }]
345
+ 'role.platform-owner': {
346
+ id: 'role.platform-owner',
347
+ order: 10,
348
+ title: 'Platform Owner',
349
+ responsibleFor: ['sys.missing']
350
+ }
297
351
  }
298
352
  })
299
353
  ).toThrow(/unknown responsibleFor system/)
@@ -302,16 +356,17 @@ describe('resolveOrganizationModel - roles cross-ref (responsibleFor and heldBy)
302
356
  it('passes when an agent holder references a declared agent resource', () => {
303
357
  expect(() =>
304
358
  resolveOrganizationModel({
305
- systems: { systems: [validSystem] },
306
- resources: { entries: [validAgentResource] },
359
+ systems: { 'sys.platform': validSystem },
360
+ resources: {
361
+ 'command-center-assistant': validAgentResource
362
+ },
307
363
  roles: {
308
- roles: [
309
- {
310
- id: 'role.platform-assistant',
311
- title: 'Platform Assistant',
312
- heldBy: { kind: 'agent', agentId: 'command-center-assistant' }
313
- }
314
- ]
364
+ 'role.platform-assistant': {
365
+ id: 'role.platform-assistant',
366
+ order: 10,
367
+ title: 'Platform Assistant',
368
+ heldBy: { kind: 'agent', agentId: 'command-center-assistant' }
369
+ }
315
370
  }
316
371
  })
317
372
  ).not.toThrow()
@@ -321,13 +376,12 @@ describe('resolveOrganizationModel - roles cross-ref (responsibleFor and heldBy)
321
376
  expect(() =>
322
377
  resolveOrganizationModel({
323
378
  roles: {
324
- roles: [
325
- {
326
- id: 'role.platform-assistant',
327
- title: 'Platform Assistant',
328
- heldBy: { kind: 'agent', agentId: 'missing-agent' }
329
- }
330
- ]
379
+ 'role.platform-assistant': {
380
+ id: 'role.platform-assistant',
381
+ order: 10,
382
+ title: 'Platform Assistant',
383
+ heldBy: { kind: 'agent', agentId: 'missing-agent' }
384
+ }
331
385
  }
332
386
  })
333
387
  ).toThrow(/unknown agent holder resource/)
@@ -336,25 +390,23 @@ describe('resolveOrganizationModel - roles cross-ref (responsibleFor and heldBy)
336
390
  it('throws when an agent holder references a non-agent resource', () => {
337
391
  expect(() =>
338
392
  resolveOrganizationModel({
339
- systems: { systems: [validSystem] },
393
+ systems: { 'sys.platform': validSystem },
340
394
  resources: {
341
- entries: [
342
- {
343
- id: 'LGN-01-company-scrape',
344
- kind: 'workflow',
345
- systemId: 'sys.platform',
346
- status: 'active'
347
- }
348
- ]
395
+ 'LGN-01-company-scrape': {
396
+ id: 'LGN-01-company-scrape',
397
+ order: 10,
398
+ kind: 'workflow' as const,
399
+ systemPath: 'sys.platform',
400
+ status: 'active' as const
401
+ }
349
402
  },
350
403
  roles: {
351
- roles: [
352
- {
353
- id: 'role.platform-assistant',
354
- title: 'Platform Assistant',
355
- heldBy: { kind: 'agent', agentId: 'LGN-01-company-scrape' }
356
- }
357
- ]
404
+ 'role.platform-assistant': {
405
+ id: 'role.platform-assistant',
406
+ order: 10,
407
+ title: 'Platform Assistant',
408
+ heldBy: { kind: 'agent', agentId: 'LGN-01-company-scrape' }
409
+ }
358
410
  }
359
411
  })
360
412
  ).toThrow(/must reference an agent resource/)
@@ -363,40 +415,49 @@ describe('resolveOrganizationModel - roles cross-ref (responsibleFor and heldBy)
363
415
 
364
416
  describe('resolveOrganizationModel - roles domain integration', () => {
365
417
  it('merges partial roles override and preserves empty roles default', () => {
366
- const model = resolveOrganizationModel({ roles: { roles: [] } })
367
- expect(model.roles.roles).toEqual([])
418
+ const model = resolveOrganizationModel({ roles: {} })
419
+ expect(Object.keys(model.roles)).toHaveLength(0)
368
420
  })
369
421
 
370
422
  it('omitting roles key entirely resolves to default empty roles', () => {
371
423
  const model = resolveOrganizationModel({})
372
424
  expect(model.roles).toBeDefined()
373
- expect(model.roles.roles).toEqual([])
425
+ expect(Object.keys(model.roles)).toHaveLength(0)
374
426
  })
375
427
 
376
428
  it('does not bleed roles changes into other top-level domains', () => {
377
429
  const model = resolveOrganizationModel({
378
430
  roles: {
379
- roles: [{ id: 'role-ceo', title: 'CEO' }]
431
+ 'role-ceo': { id: 'role-ceo', order: 10, title: 'CEO' }
380
432
  }
381
433
  })
382
434
 
383
435
  expect(model.identity).toBeDefined()
384
436
  expect(model.customers).toBeDefined()
385
437
  expect(model.offerings).toBeDefined()
386
- expect(model.features).toBeDefined()
438
+ expect(model.systems).toBeDefined()
439
+ // Phase 4 (D8): model.navigation removed from top-level OM.
387
440
  expect(model.navigation).toBeDefined()
441
+ expect(model.knowledge).toBeDefined()
388
442
  })
389
443
 
390
444
  it('resolved model includes roles domain alongside other new domains', () => {
391
445
  const model = resolveOrganizationModel({
392
- customers: { segments: [{ id: 'seg-a', name: 'Segment A' }] },
446
+ customers: {
447
+ 'seg-a': { id: 'seg-a', order: 10, name: 'Segment A' }
448
+ },
393
449
  roles: {
394
- roles: [{ id: 'role-ceo', title: 'CEO', heldBy: { kind: 'human', userId: 'user.alice' } }]
450
+ 'role-ceo': {
451
+ id: 'role-ceo',
452
+ order: 10,
453
+ title: 'CEO',
454
+ heldBy: { kind: 'human', userId: 'user.alice' }
455
+ }
395
456
  }
396
457
  })
397
458
 
398
- expect(model.customers.segments).toHaveLength(1)
399
- expect(model.roles.roles).toHaveLength(1)
400
- expect(model.roles.roles[0].heldBy).toEqual({ kind: 'human', userId: 'user.alice' })
459
+ expect(Object.keys(model.customers)).toHaveLength(1)
460
+ expect(Object.keys(model.roles)).toHaveLength(1)
461
+ expect(model.roles['role-ceo'].heldBy).toEqual({ kind: 'human', userId: 'user.alice' })
401
462
  })
402
463
  })