@elevasis/core 0.34.2 → 0.35.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.
- package/dist/auth/index.d.ts +74 -2
- package/dist/auth/index.js +65 -30
- package/dist/index.d.ts +60 -2
- package/dist/index.js +52 -1
- package/dist/knowledge/index.d.ts +12 -0
- package/dist/organization-model/index.d.ts +60 -2
- package/dist/organization-model/index.js +52 -1
- package/dist/test-utils/index.d.ts +12 -0
- package/dist/test-utils/index.js +51 -0
- package/package.json +1 -1
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +69 -30
- package/src/auth/multi-tenancy/index.ts +29 -26
- package/src/auth/multi-tenancy/org-id.test.ts +139 -0
- package/src/auth/multi-tenancy/org-id.ts +112 -0
- package/src/business/acquisition/api-schemas.test.ts +456 -28
- package/src/business/acquisition/ontology-validation.ts +715 -23
- package/src/execution/engine/tools/platform/storage/__tests__/storage.test.ts +997 -998
- package/src/organization-model/__tests__/domains/systems.test.ts +61 -15
- package/src/organization-model/__tests__/domains/topology.test.ts +23 -0
- package/src/organization-model/__tests__/schema.test.ts +112 -0
- package/src/organization-model/domains/systems.ts +44 -0
- package/src/organization-model/domains/topology.ts +18 -1
- package/src/organization-model/published.ts +19 -1
- package/src/organization-model/schema-refinements.ts +23 -0
- package/src/organization-model/types.ts +17 -1
- package/src/platform/constants/versions.ts +1 -1
- package/src/platform/registry/__tests__/validation.test.ts +218 -4
- package/src/platform/registry/index.ts +28 -15
- package/src/platform/registry/validation.ts +172 -2
- package/src/reference/_generated/contracts.md +44 -0
- package/src/supabase/__tests__/helpers.test.ts +92 -51
- package/src/supabase/helpers.ts +40 -20
- package/src/supabase/index.ts +52 -52
|
@@ -121,8 +121,15 @@ import {
|
|
|
121
121
|
} from './api-schemas'
|
|
122
122
|
import { createBuildPlanSnapshotFromTemplate } from './build-templates'
|
|
123
123
|
import {
|
|
124
|
+
compileCrmApiOntologyValidationIndex,
|
|
124
125
|
compileBusinessOntologyValidationIndex,
|
|
126
|
+
computeInterfaceReadiness,
|
|
127
|
+
compileLeadGenApiOntologyValidationIndex,
|
|
128
|
+
compileLeadGenCrmHandoffOntologyValidationIndex,
|
|
129
|
+
CRM_API_INTERFACE,
|
|
130
|
+
LEAD_GEN_CRM_HANDOFF_INTERFACE,
|
|
125
131
|
CRM_PIPELINE_CATALOG_ONTOLOGY_ID,
|
|
132
|
+
LEAD_GEN_API_INTERFACE,
|
|
126
133
|
LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID
|
|
127
134
|
} from './ontology-validation'
|
|
128
135
|
|
|
@@ -177,11 +184,142 @@ const DEFAULT_CRM_PRIORITY_RULE_CONFIG: CrmPriorityRuleConfig = {
|
|
|
177
184
|
// ---------------------------------------------------------------------------
|
|
178
185
|
// Minimal test model fixture
|
|
179
186
|
// ---------------------------------------------------------------------------
|
|
180
|
-
// Builds
|
|
181
|
-
// @repo/elevasis-core
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
// Builds OrganizationModel fixtures inline so packages/core tests have no
|
|
188
|
+
// dependency on @repo/elevasis-core canonical tenant data.
|
|
189
|
+
function buildMinimalLeadGenModel(): OrganizationModel {
|
|
190
|
+
return {
|
|
191
|
+
...DEFAULT_ORGANIZATION_MODEL,
|
|
192
|
+
systems: {
|
|
193
|
+
sales: {
|
|
194
|
+
id: 'sales',
|
|
195
|
+
label: 'Sales',
|
|
196
|
+
order: 60,
|
|
197
|
+
systems: {
|
|
198
|
+
'lead-gen': {
|
|
199
|
+
id: 'sales.lead-gen',
|
|
200
|
+
label: 'Lead Gen',
|
|
201
|
+
order: 70,
|
|
202
|
+
apiInterface: {
|
|
203
|
+
lifecycle: 'active',
|
|
204
|
+
readinessProfile: LEAD_GEN_API_INTERFACE.readinessProfile,
|
|
205
|
+
resourceIds: ['lead-gen-api-workflow']
|
|
206
|
+
},
|
|
207
|
+
ontology: {
|
|
208
|
+
objectTypes: {
|
|
209
|
+
'sales.lead-gen:object/list': {
|
|
210
|
+
id: 'sales.lead-gen:object/list',
|
|
211
|
+
label: 'Lead List',
|
|
212
|
+
ownerSystemId: 'sales.lead-gen'
|
|
213
|
+
},
|
|
214
|
+
'sales.lead-gen:object/company': {
|
|
215
|
+
id: 'sales.lead-gen:object/company',
|
|
216
|
+
label: 'Lead Company',
|
|
217
|
+
ownerSystemId: 'sales.lead-gen'
|
|
218
|
+
},
|
|
219
|
+
'sales.lead-gen:object/contact': {
|
|
220
|
+
id: 'sales.lead-gen:object/contact',
|
|
221
|
+
label: 'Lead Contact',
|
|
222
|
+
ownerSystemId: 'sales.lead-gen'
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
catalogTypes: {
|
|
226
|
+
'sales.lead-gen:catalog/build-template': {
|
|
227
|
+
id: 'sales.lead-gen:catalog/build-template',
|
|
228
|
+
label: 'Build Templates',
|
|
229
|
+
ownerSystemId: 'sales.lead-gen',
|
|
230
|
+
kind: 'template',
|
|
231
|
+
appliesTo: 'sales.lead-gen:object/list',
|
|
232
|
+
entries: {
|
|
233
|
+
'local-services': {
|
|
234
|
+
label: 'Local Services',
|
|
235
|
+
order: 10,
|
|
236
|
+
stepCatalog: 'sales.lead-gen:catalog/template-step.local-services'
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
'sales.lead-gen:catalog/template-step.local-services': {
|
|
241
|
+
id: 'sales.lead-gen:catalog/template-step.local-services',
|
|
242
|
+
label: 'Local Services Steps',
|
|
243
|
+
ownerSystemId: 'sales.lead-gen',
|
|
244
|
+
kind: 'template-step',
|
|
245
|
+
appliesTo: 'sales.lead-gen:object/list',
|
|
246
|
+
entries: {
|
|
247
|
+
'source-companies': {
|
|
248
|
+
label: 'Source Companies',
|
|
249
|
+
order: 10,
|
|
250
|
+
primaryEntity: 'company',
|
|
251
|
+
outputs: ['company'],
|
|
252
|
+
stageKey: 'populated',
|
|
253
|
+
dependencyMode: 'per-record-eligibility',
|
|
254
|
+
actionKey: 'lead-gen.company.source'
|
|
255
|
+
},
|
|
256
|
+
'discover-contacts': {
|
|
257
|
+
label: 'Discover Contacts',
|
|
258
|
+
order: 20,
|
|
259
|
+
primaryEntity: 'contact',
|
|
260
|
+
outputs: ['contact'],
|
|
261
|
+
stageKey: 'discovered',
|
|
262
|
+
dependencyMode: 'per-record-eligibility',
|
|
263
|
+
actionKey: 'lead-gen.contact.discover'
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
'sales.lead-gen:catalog/company-stage': {
|
|
268
|
+
id: 'sales.lead-gen:catalog/company-stage',
|
|
269
|
+
label: 'Company Stages',
|
|
270
|
+
ownerSystemId: 'sales.lead-gen',
|
|
271
|
+
kind: 'stage',
|
|
272
|
+
appliesTo: 'sales.lead-gen:object/company',
|
|
273
|
+
entries: {
|
|
274
|
+
populated: { label: 'Populated', description: 'Companies imported.', order: 10 },
|
|
275
|
+
qualified: { label: 'Qualified', description: 'Companies qualified.', order: 20 }
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
'sales.lead-gen:catalog/contact-stage': {
|
|
279
|
+
id: 'sales.lead-gen:catalog/contact-stage',
|
|
280
|
+
label: 'Contact Stages',
|
|
281
|
+
ownerSystemId: 'sales.lead-gen',
|
|
282
|
+
kind: 'stage',
|
|
283
|
+
appliesTo: 'sales.lead-gen:object/contact',
|
|
284
|
+
entries: {
|
|
285
|
+
discovered: { label: 'Discovered', description: 'Contacts found.', order: 10 },
|
|
286
|
+
verified: { label: 'Verified', description: 'Contacts verified.', order: 20 }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
resources: {
|
|
296
|
+
'lead-gen-api-workflow': {
|
|
297
|
+
id: 'lead-gen-api-workflow',
|
|
298
|
+
kind: 'workflow',
|
|
299
|
+
systemPath: 'sales.lead-gen',
|
|
300
|
+
status: 'active',
|
|
301
|
+
order: 10,
|
|
302
|
+
ontology: {
|
|
303
|
+
actions: ['sales.lead-gen:action/api'],
|
|
304
|
+
primaryAction: 'sales.lead-gen:action/api',
|
|
305
|
+
reads: [
|
|
306
|
+
'sales.lead-gen:object/list',
|
|
307
|
+
'sales.lead-gen:object/company',
|
|
308
|
+
'sales.lead-gen:object/contact'
|
|
309
|
+
],
|
|
310
|
+
usesCatalogs: [
|
|
311
|
+
'sales.lead-gen:catalog/build-template',
|
|
312
|
+
'sales.lead-gen:catalog/template-step.local-services',
|
|
313
|
+
'sales.lead-gen:catalog/company-stage',
|
|
314
|
+
'sales.lead-gen:catalog/contact-stage'
|
|
315
|
+
]
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function buildMinimalLeadGenAndCrmModel(options: { includeHandoff?: boolean } = {}): OrganizationModel {
|
|
185
323
|
const crmCatalogEntries = Object.fromEntries(
|
|
186
324
|
CRM_PIPELINE_DEFINITION.stages.map((stage, idx) => [
|
|
187
325
|
stage.stageKey,
|
|
@@ -194,34 +332,119 @@ function buildMinimalCrmModel(): OrganizationModel {
|
|
|
194
332
|
}
|
|
195
333
|
])
|
|
196
334
|
)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
335
|
+
const model = buildMinimalLeadGenModel()
|
|
336
|
+
const salesSystem = model.systems.sales
|
|
337
|
+
if (salesSystem === undefined) throw new Error('Minimal lead-gen fixture is missing sales system')
|
|
338
|
+
|
|
339
|
+
salesSystem.systems = {
|
|
340
|
+
...(salesSystem.systems ?? {}),
|
|
341
|
+
crm: {
|
|
342
|
+
id: 'sales.crm',
|
|
343
|
+
label: 'CRM',
|
|
344
|
+
order: 80,
|
|
345
|
+
apiInterface: {
|
|
346
|
+
lifecycle: 'active',
|
|
347
|
+
readinessProfile: CRM_API_INTERFACE.readinessProfile,
|
|
348
|
+
resourceIds: ['crm-api-workflow']
|
|
349
|
+
},
|
|
350
|
+
ontology: {
|
|
351
|
+
objectTypes: {
|
|
352
|
+
'sales.crm:object/deal': {
|
|
353
|
+
id: 'sales.crm:object/deal',
|
|
354
|
+
label: 'Deal',
|
|
355
|
+
ownerSystemId: 'sales.crm'
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
catalogTypes: {
|
|
359
|
+
'sales.crm:catalog/crm.pipeline': {
|
|
360
|
+
id: 'sales.crm:catalog/crm.pipeline',
|
|
207
361
|
label: 'CRM',
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
362
|
+
ownerSystemId: 'sales.crm',
|
|
363
|
+
kind: 'pipeline',
|
|
364
|
+
entries: crmCatalogEntries
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
model.resources = {
|
|
372
|
+
...model.resources,
|
|
373
|
+
'crm-api-workflow': {
|
|
374
|
+
id: 'crm-api-workflow',
|
|
375
|
+
kind: 'workflow',
|
|
376
|
+
systemPath: 'sales.crm',
|
|
377
|
+
status: 'active',
|
|
378
|
+
order: 20,
|
|
379
|
+
ontology: {
|
|
380
|
+
actions: ['sales.crm:action/api'],
|
|
381
|
+
primaryAction: 'sales.crm:action/api',
|
|
382
|
+
usesCatalogs: ['sales.crm:catalog/crm.pipeline']
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (options.includeHandoff === true) {
|
|
388
|
+
const leadGenSystem = salesSystem.systems['lead-gen']
|
|
389
|
+
if (leadGenSystem === undefined) throw new Error('Minimal fixture is missing lead-gen system')
|
|
390
|
+
model.resources = {
|
|
391
|
+
...model.resources,
|
|
392
|
+
'lead-gen-crm-handoff-workflow': {
|
|
393
|
+
id: 'lead-gen-crm-handoff-workflow',
|
|
394
|
+
kind: 'workflow',
|
|
395
|
+
systemPath: 'sales.lead-gen',
|
|
396
|
+
status: 'active',
|
|
397
|
+
order: 30,
|
|
398
|
+
ontology: {
|
|
399
|
+
actions: ['sales.lead-gen:action/handoff'],
|
|
400
|
+
primaryAction: 'sales.lead-gen:action/handoff',
|
|
401
|
+
reads: [
|
|
402
|
+
'sales.lead-gen:object/list',
|
|
403
|
+
'sales.lead-gen:object/company',
|
|
404
|
+
'sales.lead-gen:object/contact'
|
|
405
|
+
],
|
|
406
|
+
usesCatalogs: [
|
|
407
|
+
'sales.lead-gen:catalog/build-template',
|
|
408
|
+
'sales.lead-gen:catalog/template-step.local-services',
|
|
409
|
+
'sales.lead-gen:catalog/company-stage',
|
|
410
|
+
'sales.lead-gen:catalog/contact-stage',
|
|
411
|
+
'sales.crm:catalog/crm.pipeline'
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
model.topology = {
|
|
417
|
+
version: 1,
|
|
418
|
+
relationships: {
|
|
419
|
+
'lead-gen-crm-handoff': {
|
|
420
|
+
from: { kind: 'resource', id: 'lead-gen-crm-handoff-workflow' },
|
|
421
|
+
kind: 'uses',
|
|
422
|
+
to: { kind: 'resource', id: 'crm-api-workflow' },
|
|
423
|
+
required: true,
|
|
424
|
+
metadata: {
|
|
425
|
+
systemInterfaceGrant: {
|
|
426
|
+
consumer: {
|
|
427
|
+
systemPath: LEAD_GEN_CRM_HANDOFF_INTERFACE.systemPath,
|
|
428
|
+
interfaceKey: LEAD_GEN_CRM_HANDOFF_INTERFACE.interfaceKey
|
|
429
|
+
},
|
|
430
|
+
provider: {
|
|
431
|
+
systemPath: CRM_API_INTERFACE.systemPath,
|
|
432
|
+
interfaceKey: CRM_API_INTERFACE.interfaceKey
|
|
433
|
+
},
|
|
434
|
+
resourceIds: ['lead-gen-crm-handoff-workflow'],
|
|
435
|
+
ontologyIds: ['sales.crm:catalog/crm.pipeline']
|
|
219
436
|
}
|
|
220
437
|
}
|
|
221
438
|
}
|
|
222
439
|
}
|
|
223
440
|
}
|
|
224
441
|
}
|
|
442
|
+
|
|
443
|
+
return model
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function buildMinimalCrmModel(): OrganizationModel {
|
|
447
|
+
return buildMinimalLeadGenAndCrmModel()
|
|
225
448
|
}
|
|
226
449
|
|
|
227
450
|
// ---------------------------------------------------------------------------
|
|
@@ -276,10 +499,215 @@ describe('DealStageSchema', () => {
|
|
|
276
499
|
})
|
|
277
500
|
|
|
278
501
|
// ---------------------------------------------------------------------------
|
|
279
|
-
// Ontology validation
|
|
502
|
+
// Ontology validation contracts
|
|
280
503
|
// ---------------------------------------------------------------------------
|
|
281
504
|
|
|
282
|
-
describe('business ontology validation
|
|
505
|
+
describe('business ontology validation contracts', () => {
|
|
506
|
+
it('computes structured readiness for a ready lead-gen API interface without CRM', () => {
|
|
507
|
+
const model = buildMinimalLeadGenModel()
|
|
508
|
+
const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)
|
|
509
|
+
|
|
510
|
+
expect(readiness.ready).toBe(true)
|
|
511
|
+
expect(readiness.readinessProfile).toBe(LEAD_GEN_API_INTERFACE.readinessProfile)
|
|
512
|
+
expect(readiness.scopedResourceIds).toEqual(['lead-gen-api-workflow'])
|
|
513
|
+
expect(readiness.issues).toEqual([])
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
it('lead-gen API validation passes without CRM when list/company/contact readiness exists', () => {
|
|
517
|
+
const model = buildMinimalLeadGenModel()
|
|
518
|
+
const index = compileLeadGenApiOntologyValidationIndex(model)
|
|
519
|
+
|
|
520
|
+
expect(index.ontology.catalogTypes[CRM_PIPELINE_CATALOG_ONTOLOGY_ID]).toBeUndefined()
|
|
521
|
+
expect(index.ontology.catalogTypes[LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID]).toBeDefined()
|
|
522
|
+
expect(index.leadGenStageCatalog).toBeDefined()
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
it('CRM API validation supplies pipeline readiness without requiring lead-gen contracts', () => {
|
|
526
|
+
const model = buildMinimalCrmModel()
|
|
527
|
+
const index = compileCrmApiOntologyValidationIndex(model)
|
|
528
|
+
|
|
529
|
+
expect(index.crmPipelineCatalog).toBeDefined()
|
|
530
|
+
expect(index.crmPipelineCatalog.entries).toHaveProperty('interested')
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
it('computes structured diagnostics when an interface marker is missing', () => {
|
|
534
|
+
const model = buildMinimalLeadGenAndCrmModel()
|
|
535
|
+
delete model.systems.sales?.systems?.['lead-gen']?.apiInterface
|
|
536
|
+
|
|
537
|
+
const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)
|
|
538
|
+
|
|
539
|
+
expect(readiness.ready).toBe(false)
|
|
540
|
+
expect(readiness.issues).toMatchObject([{ family: 'SYSTEM_INTERFACE_MISSING', code: 'missing-interface' }])
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
it('computes structured diagnostics when an interface marker is inactive', () => {
|
|
544
|
+
const model = buildMinimalLeadGenModel()
|
|
545
|
+
const apiInterface = model.systems.sales?.systems?.['lead-gen']?.apiInterface
|
|
546
|
+
if (apiInterface === undefined) throw new Error('Minimal fixture is missing lead-gen API interface')
|
|
547
|
+
apiInterface.lifecycle = 'disabled'
|
|
548
|
+
|
|
549
|
+
const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)
|
|
550
|
+
|
|
551
|
+
expect(readiness.ready).toBe(false)
|
|
552
|
+
expect(readiness.issues).toEqual(
|
|
553
|
+
expect.arrayContaining([expect.objectContaining({ family: 'SYSTEM_INTERFACE_DISABLED', code: 'inactive-interface' })])
|
|
554
|
+
)
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
it('computes structured diagnostics when scoped API resources are absent', () => {
|
|
558
|
+
const model = buildMinimalLeadGenModel()
|
|
559
|
+
const apiInterface = model.systems.sales?.systems?.['lead-gen']?.apiInterface
|
|
560
|
+
if (apiInterface === undefined) throw new Error('Minimal fixture is missing lead-gen API interface')
|
|
561
|
+
apiInterface.resourceIds = []
|
|
562
|
+
|
|
563
|
+
const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)
|
|
564
|
+
|
|
565
|
+
expect(readiness.ready).toBe(false)
|
|
566
|
+
expect(readiness.issues).toEqual(
|
|
567
|
+
expect.arrayContaining([
|
|
568
|
+
expect.objectContaining({ family: 'SYSTEM_INTERFACE_NOT_READY', code: 'missing-scoped-resources' })
|
|
569
|
+
])
|
|
570
|
+
)
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
it('computes structured diagnostics when a scoped API resource id is missing', () => {
|
|
574
|
+
const model = buildMinimalLeadGenModel()
|
|
575
|
+
const apiInterface = model.systems.sales?.systems?.['lead-gen']?.apiInterface
|
|
576
|
+
if (apiInterface === undefined) throw new Error('Minimal fixture is missing lead-gen API interface')
|
|
577
|
+
apiInterface.resourceIds = ['missing-lead-gen-api-workflow']
|
|
578
|
+
|
|
579
|
+
const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)
|
|
580
|
+
|
|
581
|
+
expect(readiness.ready).toBe(false)
|
|
582
|
+
expect(readiness.issues).toEqual(
|
|
583
|
+
expect.arrayContaining([
|
|
584
|
+
expect.objectContaining({
|
|
585
|
+
family: 'SYSTEM_INTERFACE_NOT_READY',
|
|
586
|
+
code: 'missing-resource',
|
|
587
|
+
ref: 'missing-lead-gen-api-workflow',
|
|
588
|
+
path: 'systems.sales.lead-gen.apiInterface.resourceIds.0'
|
|
589
|
+
})
|
|
590
|
+
])
|
|
591
|
+
)
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
it('computes structured diagnostics when a scoped API resource belongs to another System', () => {
|
|
595
|
+
const model = buildMinimalLeadGenAndCrmModel()
|
|
596
|
+
const apiInterface = model.systems.sales?.systems?.['lead-gen']?.apiInterface
|
|
597
|
+
if (apiInterface === undefined) throw new Error('Minimal fixture is missing lead-gen API interface')
|
|
598
|
+
apiInterface.resourceIds = ['crm-api-workflow']
|
|
599
|
+
|
|
600
|
+
const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)
|
|
601
|
+
|
|
602
|
+
expect(readiness.ready).toBe(false)
|
|
603
|
+
expect(readiness.issues).toEqual(
|
|
604
|
+
expect.arrayContaining([
|
|
605
|
+
expect.objectContaining({
|
|
606
|
+
family: 'SYSTEM_INTERFACE_INVALID',
|
|
607
|
+
code: 'resource-system-mismatch',
|
|
608
|
+
ref: 'crm-api-workflow',
|
|
609
|
+
path: 'systems.sales.lead-gen.apiInterface.resourceIds.0'
|
|
610
|
+
})
|
|
611
|
+
])
|
|
612
|
+
)
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
it('computes structured diagnostics when required ontology exists but no scoped resource binds it', () => {
|
|
616
|
+
const model = buildMinimalLeadGenModel()
|
|
617
|
+
const resource = model.resources['lead-gen-api-workflow']
|
|
618
|
+
if (resource === undefined) throw new Error('Minimal fixture is missing lead-gen API resource')
|
|
619
|
+
resource.ontology = {
|
|
620
|
+
...resource.ontology,
|
|
621
|
+
reads: ['sales.lead-gen:object/list', 'sales.lead-gen:object/company']
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)
|
|
625
|
+
|
|
626
|
+
expect(readiness.ready).toBe(false)
|
|
627
|
+
expect(readiness.issues).toEqual(
|
|
628
|
+
expect.arrayContaining([
|
|
629
|
+
expect.objectContaining({
|
|
630
|
+
family: 'SYSTEM_INTERFACE_NOT_READY',
|
|
631
|
+
code: 'missing-resource-binding',
|
|
632
|
+
ref: 'sales.lead-gen:object/contact'
|
|
633
|
+
})
|
|
634
|
+
])
|
|
635
|
+
)
|
|
636
|
+
})
|
|
637
|
+
|
|
638
|
+
it('computes structured diagnostics when required content is empty', () => {
|
|
639
|
+
const model = buildMinimalLeadGenModel()
|
|
640
|
+
const catalog = model.systems.sales?.systems?.['lead-gen']?.ontology?.catalogTypes?.[
|
|
641
|
+
'sales.lead-gen:catalog/build-template'
|
|
642
|
+
]
|
|
643
|
+
if (catalog === undefined) throw new Error('Minimal fixture is missing build-template catalog')
|
|
644
|
+
catalog.entries = {}
|
|
645
|
+
|
|
646
|
+
const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)
|
|
647
|
+
|
|
648
|
+
expect(readiness.ready).toBe(false)
|
|
649
|
+
expect(readiness.issues).toEqual(
|
|
650
|
+
expect.arrayContaining([expect.objectContaining({ family: 'SYSTEM_INTERFACE_NOT_READY', code: 'empty-catalog' })])
|
|
651
|
+
)
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
it('computes structured diagnostics for an unknown readiness profile', () => {
|
|
655
|
+
const model = buildMinimalLeadGenModel()
|
|
656
|
+
const apiInterface = model.systems.sales?.systems?.['lead-gen']?.apiInterface
|
|
657
|
+
if (apiInterface === undefined) throw new Error('Minimal fixture is missing lead-gen API interface')
|
|
658
|
+
apiInterface.readinessProfile = 'sales.lead-gen.unknown'
|
|
659
|
+
|
|
660
|
+
const readiness = computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE)
|
|
661
|
+
|
|
662
|
+
expect(readiness.ready).toBe(false)
|
|
663
|
+
expect(readiness.issues).toMatchObject([{ family: 'SYSTEM_INTERFACE_INVALID', code: 'unknown-readiness-profile' }])
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
it('lead-gen-to-CRM handoff validation fails when the scoped topology grant is absent', () => {
|
|
667
|
+
const model = buildMinimalLeadGenAndCrmModel({ includeHandoff: true })
|
|
668
|
+
model.topology.relationships = {}
|
|
669
|
+
|
|
670
|
+
expect(() => compileLeadGenCrmHandoffOntologyValidationIndex(model)).toThrow('missing-topology-grant')
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
it('lead-gen-to-CRM handoff readiness reports provider interface failures as bridge diagnostics', () => {
|
|
674
|
+
const model = buildMinimalLeadGenAndCrmModel({ includeHandoff: true })
|
|
675
|
+
const crmInterface = model.systems.sales?.systems?.crm?.apiInterface
|
|
676
|
+
if (crmInterface === undefined) throw new Error('Minimal fixture is missing CRM API interface')
|
|
677
|
+
crmInterface.lifecycle = 'disabled'
|
|
678
|
+
|
|
679
|
+
const readiness = computeInterfaceReadiness(model, LEAD_GEN_CRM_HANDOFF_INTERFACE)
|
|
680
|
+
|
|
681
|
+
expect(readiness.ready).toBe(false)
|
|
682
|
+
expect(readiness.issues).toEqual(
|
|
683
|
+
expect.arrayContaining([
|
|
684
|
+
expect.objectContaining({
|
|
685
|
+
family: 'SYSTEM_BRIDGE_NOT_READY',
|
|
686
|
+
code: 'inactive-interface',
|
|
687
|
+
path: 'systems.sales.crm.apiInterface.lifecycle'
|
|
688
|
+
})
|
|
689
|
+
])
|
|
690
|
+
)
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
it('lead-gen-to-CRM handoff validation requires the scoped topology grant and CRM pipeline readiness', () => {
|
|
694
|
+
const model = buildMinimalLeadGenAndCrmModel({ includeHandoff: true })
|
|
695
|
+
const index = compileLeadGenCrmHandoffOntologyValidationIndex(model)
|
|
696
|
+
|
|
697
|
+
expect(index.bridgeGrant).toBeDefined()
|
|
698
|
+
expect(index.leadGenStageCatalog).toBeDefined()
|
|
699
|
+
expect(index.crmPipelineCatalog).toBeDefined()
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
it('lead-gen-to-CRM handoff validation fails when CRM pipeline readiness is absent', () => {
|
|
703
|
+
const model = buildMinimalLeadGenAndCrmModel({ includeHandoff: true })
|
|
704
|
+
const crmSystem = model.systems.sales?.systems?.crm
|
|
705
|
+
if (crmSystem?.ontology?.catalogTypes === undefined) throw new Error('Minimal fixture is missing CRM catalog types')
|
|
706
|
+
delete crmSystem.ontology.catalogTypes['sales.crm:catalog/crm.pipeline']
|
|
707
|
+
|
|
708
|
+
expect(() => compileLeadGenCrmHandoffOntologyValidationIndex(model)).toThrow(CRM_PIPELINE_CATALOG_ONTOLOGY_ID)
|
|
709
|
+
})
|
|
710
|
+
|
|
283
711
|
// compileBusinessOntologyValidationIndex now requires a model arg — no default singleton.
|
|
284
712
|
// The canonical Elevasis CRM pipeline catalog is model-owned (authored in @repo/elevasis-core
|
|
285
713
|
// canonicalOrganizationModel at 'sales.crm:catalog/crm.pipeline'). Tests here use a
|