@elevasis/core 0.25.0 → 0.26.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/index.d.ts +166 -85
- package/dist/index.js +146 -1346
- package/dist/knowledge/index.d.ts +27 -38
- package/dist/knowledge/index.js +1 -1
- package/dist/organization-model/index.d.ts +166 -85
- package/dist/organization-model/index.js +146 -1346
- package/dist/test-utils/index.d.ts +23 -31
- package/dist/test-utils/index.js +75 -1238
- package/package.json +1 -1
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +14 -2
- package/src/business/acquisition/api-schemas.test.ts +70 -77
- package/src/business/acquisition/api-schemas.ts +21 -42
- package/src/business/acquisition/derive-actions.test.ts +11 -21
- package/src/business/acquisition/derive-actions.ts +61 -14
- package/src/business/acquisition/ontology-validation.ts +4 -4
- package/src/business/acquisition/types.ts +7 -8
- package/src/knowledge/queries.ts +0 -1
- package/src/organization-model/__tests__/content-kinds-registry.test.ts +35 -210
- package/src/organization-model/__tests__/defaults.test.ts +4 -4
- package/src/organization-model/__tests__/domains/actions.test.ts +12 -36
- package/src/organization-model/__tests__/domains/offerings.test.ts +13 -6
- package/src/organization-model/__tests__/domains/resources.test.ts +497 -350
- package/src/organization-model/__tests__/domains/systems.test.ts +6 -7
- package/src/organization-model/__tests__/flatten-additive-merge.test.ts +68 -80
- package/src/organization-model/__tests__/foundation.test.ts +81 -14
- package/src/organization-model/__tests__/graph.test.ts +662 -694
- package/src/organization-model/__tests__/knowledge.test.ts +31 -17
- package/src/organization-model/__tests__/lookup-helpers.test.ts +128 -438
- package/src/organization-model/__tests__/migration-helpers.test.ts +362 -591
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +68 -103
- package/src/organization-model/__tests__/published-zero-leak.test.ts +17 -0
- package/src/organization-model/__tests__/recursive-system-schema.test.ts +159 -532
- package/src/organization-model/__tests__/resolve.test.ts +79 -42
- package/src/organization-model/__tests__/schema.test.ts +65 -56
- package/src/organization-model/catalogs/lead-gen.ts +0 -103
- package/src/organization-model/defaults.ts +17 -702
- package/src/organization-model/domains/actions.ts +116 -333
- package/src/organization-model/domains/knowledge.ts +15 -7
- package/src/organization-model/domains/projects.ts +4 -4
- package/src/organization-model/domains/prospecting.ts +405 -395
- package/src/organization-model/domains/resources.ts +206 -135
- package/src/organization-model/domains/sales.ts +5 -5
- package/src/organization-model/domains/systems.ts +8 -23
- package/src/organization-model/graph/build.ts +223 -294
- package/src/organization-model/graph/schema.ts +2 -3
- package/src/organization-model/graph/types.ts +12 -14
- package/src/organization-model/helpers.ts +130 -218
- package/src/organization-model/index.ts +104 -124
- package/src/organization-model/migration-helpers.ts +211 -249
- package/src/organization-model/ontology.ts +0 -60
- package/src/organization-model/organization-graph.mdx +4 -5
- package/src/organization-model/organization-model.mdx +1 -1
- package/src/organization-model/published.ts +236 -226
- package/src/organization-model/resolve.ts +4 -5
- package/src/organization-model/schema.ts +610 -704
- package/src/organization-model/types.ts +167 -161
- package/src/platform/registry/__tests__/validation.test.ts +23 -0
- package/src/platform/registry/validation.ts +13 -2
- package/src/reference/_generated/contracts.md +14 -2
- package/src/organization-model/content-kinds/config.ts +0 -36
- package/src/organization-model/content-kinds/index.ts +0 -78
- package/src/organization-model/content-kinds/pipeline.ts +0 -68
- package/src/organization-model/content-kinds/registry.ts +0 -44
- package/src/organization-model/content-kinds/status.ts +0 -71
- package/src/organization-model/content-kinds/template.ts +0 -83
- package/src/organization-model/content-kinds/types.ts +0 -117
|
@@ -1,36 +1,38 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { ZodError } from 'zod'
|
|
3
|
-
import { bindResourceDescriptor } from '../../../platform/registry/types'
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { ZodError } from 'zod'
|
|
3
|
+
import { bindResourceDescriptor } from '../../../platform/registry/types'
|
|
4
|
+
import {
|
|
5
|
+
ContractRefSchema,
|
|
6
|
+
DEFAULT_ORGANIZATION_MODEL_RESOURCES,
|
|
7
|
+
EventIdSchema,
|
|
7
8
|
ResourceEntrySchema,
|
|
8
9
|
ResourceKindSchema,
|
|
9
10
|
ResourceOntologyBindingSchema,
|
|
10
11
|
ResourcesDomainSchema,
|
|
11
|
-
defineResource,
|
|
12
|
-
defineResources
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
12
|
+
defineResource,
|
|
13
|
+
defineResources,
|
|
14
|
+
parseContractRef
|
|
15
|
+
} from '../../domains/resources'
|
|
16
|
+
import { resolveOrganizationModel } from '../../resolve'
|
|
17
|
+
|
|
18
|
+
const VALID_SYSTEM = {
|
|
19
|
+
id: 'sys.lead-gen',
|
|
20
|
+
order: 10,
|
|
21
|
+
title: 'Lead Generation Pipeline',
|
|
22
|
+
description: 'Coordinates prospecting, enrichment, qualification, and outreach preparation.',
|
|
23
|
+
kind: 'operational' as const,
|
|
24
|
+
status: 'active' as const
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const VALID_ROLE = {
|
|
28
|
+
id: 'role.sales-ops',
|
|
29
|
+
order: 10,
|
|
30
|
+
title: 'Sales Ops'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const WORKFLOW_RESOURCE = {
|
|
34
|
+
id: 'LGN-01-company-scrape',
|
|
35
|
+
order: 10,
|
|
34
36
|
kind: 'workflow' as const,
|
|
35
37
|
systemPath: 'sys.lead-gen',
|
|
36
38
|
title: 'Company Scrape',
|
|
@@ -38,195 +40,195 @@ const WORKFLOW_RESOURCE = {
|
|
|
38
40
|
ownerRoleId: 'role.sales-ops',
|
|
39
41
|
status: 'active' as const,
|
|
40
42
|
codeRefs: [
|
|
41
|
-
{
|
|
42
|
-
path: 'operations/src/lead-gen/company-scrape/index.ts',
|
|
43
|
-
role: 'entrypoint' as const,
|
|
44
|
-
symbol: 'companyScrapeWorkflow',
|
|
45
|
-
description: 'Workflow definition bound to this OM resource descriptor.'
|
|
46
|
-
}
|
|
47
|
-
]
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const WORKFLOW_EVENT_EMISSION = {
|
|
51
|
-
eventKey: 'processed',
|
|
52
|
-
label: 'Processed',
|
|
53
|
-
payloadSchema: 'leadgen.company.processed',
|
|
54
|
-
lifecycle: 'active' as const
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const AGENT_RESOURCE = {
|
|
58
|
-
id: 'command-center-assistant',
|
|
59
|
-
order: 20,
|
|
60
|
-
kind: 'agent' as const,
|
|
61
|
-
systemPath: 'sys.lead-gen',
|
|
62
|
-
ownerRoleId: 'role.sales-ops',
|
|
63
|
-
status: 'active' as const,
|
|
64
|
-
agentKind: 'platform' as const,
|
|
65
|
-
actsAsRoleId: 'role.sales-ops',
|
|
66
|
-
sessionCapable: true,
|
|
67
|
-
invocations: [{ kind: 'slash-command' as const, command: '/work handoff' }]
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const INTEGRATION_RESOURCE = {
|
|
71
|
-
id: 'attio-crm',
|
|
72
|
-
order: 30,
|
|
73
|
-
kind: 'integration' as const,
|
|
74
|
-
systemPath: 'sys.lead-gen',
|
|
75
|
-
ownerRoleId: 'role.sales-ops',
|
|
76
|
-
status: 'active' as const,
|
|
77
|
-
provider: 'attio'
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const SCRIPT_RESOURCE = {
|
|
81
|
-
id: 'leadgen-backfill-script',
|
|
82
|
-
order: 40,
|
|
83
|
-
kind: 'script' as const,
|
|
84
|
-
systemPath: 'sys.lead-gen',
|
|
85
|
-
ownerRoleId: 'role.sales-ops',
|
|
86
|
-
status: 'active' as const,
|
|
87
|
-
language: 'typescript' as const,
|
|
88
|
-
source: {
|
|
89
|
-
file: 'scripts/leadgen/backfill.ts'
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function resolveWithResources(resources: Record<string, unknown>) {
|
|
94
|
-
return resolveOrganizationModel({
|
|
95
|
-
roles: { 'role.sales-ops': VALID_ROLE },
|
|
96
|
-
systems: { 'sys.lead-gen': VALID_SYSTEM },
|
|
97
|
-
resources: resources as Record<string, typeof WORKFLOW_RESOURCE>
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
describe('ResourceEntrySchema', () => {
|
|
102
|
-
it('accepts workflow, agent, integration, and script descriptors with required systemPath', () => {
|
|
103
|
-
expect(ResourceEntrySchema.safeParse(WORKFLOW_RESOURCE).success).toBe(true)
|
|
104
|
-
expect(ResourceEntrySchema.safeParse(AGENT_RESOURCE).success).toBe(true)
|
|
105
|
-
expect(ResourceEntrySchema.safeParse(INTEGRATION_RESOURCE).success).toBe(true)
|
|
106
|
-
expect(ResourceEntrySchema.safeParse(SCRIPT_RESOURCE).success).toBe(true)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('rejects resources without a systemPath', () => {
|
|
110
|
-
const { systemPath: _systemPath, ...resourceWithoutSystem } = WORKFLOW_RESOURCE
|
|
111
|
-
|
|
112
|
-
expect(ResourceEntrySchema.safeParse(resourceWithoutSystem).success).toBe(false)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it('defaults codeRefs and accepts resource-level implementation breadcrumbs', () => {
|
|
116
|
-
const withCodeRefs = ResourceEntrySchema.safeParse(WORKFLOW_RESOURCE)
|
|
117
|
-
const withoutCodeRefs = ResourceEntrySchema.safeParse({
|
|
118
|
-
...WORKFLOW_RESOURCE,
|
|
119
|
-
codeRefs: undefined
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
expect(withCodeRefs.success).toBe(true)
|
|
123
|
-
expect(withoutCodeRefs.success).toBe(true)
|
|
124
|
-
|
|
125
|
-
if (withCodeRefs.success) {
|
|
126
|
-
expect(withCodeRefs.data.codeRefs).toEqual(WORKFLOW_RESOURCE.codeRefs)
|
|
127
|
-
}
|
|
128
|
-
if (withoutCodeRefs.success) {
|
|
129
|
-
expect(withoutCodeRefs.data.codeRefs).toEqual([])
|
|
130
|
-
}
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('rejects codeRefs with an empty path', () => {
|
|
134
|
-
expect(
|
|
135
|
-
ResourceEntrySchema.safeParse({
|
|
136
|
-
...WORKFLOW_RESOURCE,
|
|
137
|
-
codeRefs: [
|
|
138
|
-
{
|
|
139
|
-
path: ' ',
|
|
140
|
-
role: 'entrypoint'
|
|
141
|
-
}
|
|
142
|
-
]
|
|
143
|
-
}).success
|
|
144
|
-
).toBe(false)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('rejects codeRefs with an invalid role', () => {
|
|
148
|
-
expect(
|
|
149
|
-
ResourceEntrySchema.safeParse({
|
|
150
|
-
...WORKFLOW_RESOURCE,
|
|
151
|
-
codeRefs: [
|
|
152
|
-
{
|
|
153
|
-
path: 'operations/src/lead-gen/company-scrape/index.ts',
|
|
154
|
-
role: 'runtime'
|
|
155
|
-
}
|
|
156
|
-
]
|
|
157
|
-
}).success
|
|
158
|
-
).toBe(false)
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('rejects multi-system membership', () => {
|
|
162
|
-
expect(
|
|
163
|
-
ResourceEntrySchema.safeParse({
|
|
164
|
-
...WORKFLOW_RESOURCE,
|
|
165
|
-
systemPath: ['sys.lead-gen', 'sys.crm']
|
|
166
|
-
}).success
|
|
167
|
-
).toBe(false)
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
it('rejects an agent kind outside the code-side mirror enum', () => {
|
|
171
|
-
expect(
|
|
172
|
-
ResourceEntrySchema.safeParse({
|
|
173
|
-
...AGENT_RESOURCE,
|
|
174
|
-
agentKind: 'runner'
|
|
175
|
-
}).success
|
|
176
|
-
).toBe(false)
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('defaults agent invocations and accepts slash-command orchestration entries', () => {
|
|
180
|
-
const result = ResourceEntrySchema.safeParse({
|
|
181
|
-
...AGENT_RESOURCE,
|
|
182
|
-
invocations: undefined
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
expect(result.success).toBe(true)
|
|
186
|
-
if (result.success) {
|
|
187
|
-
expect(result.data.kind).toBe('agent')
|
|
188
|
-
if (result.data.kind === 'agent') {
|
|
189
|
-
expect(result.data.invocations).toEqual([])
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
expect(ResourceEntrySchema.safeParse(AGENT_RESOURCE).success).toBe(true)
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
it('rejects malformed agent invocation entries', () => {
|
|
197
|
-
expect(
|
|
198
|
-
ResourceEntrySchema.safeParse({
|
|
199
|
-
...AGENT_RESOURCE,
|
|
200
|
-
invocations: [{ kind: 'slash-command', command: 'work handoff' }]
|
|
201
|
-
}).success
|
|
202
|
-
).toBe(false)
|
|
203
|
-
})
|
|
204
|
-
|
|
43
|
+
{
|
|
44
|
+
path: 'operations/src/lead-gen/company-scrape/index.ts',
|
|
45
|
+
role: 'entrypoint' as const,
|
|
46
|
+
symbol: 'companyScrapeWorkflow',
|
|
47
|
+
description: 'Workflow definition bound to this OM resource descriptor.'
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const WORKFLOW_EVENT_EMISSION = {
|
|
53
|
+
eventKey: 'processed',
|
|
54
|
+
label: 'Processed',
|
|
55
|
+
payloadSchema: 'leadgen.company.processed',
|
|
56
|
+
lifecycle: 'active' as const
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const AGENT_RESOURCE = {
|
|
60
|
+
id: 'command-center-assistant',
|
|
61
|
+
order: 20,
|
|
62
|
+
kind: 'agent' as const,
|
|
63
|
+
systemPath: 'sys.lead-gen',
|
|
64
|
+
ownerRoleId: 'role.sales-ops',
|
|
65
|
+
status: 'active' as const,
|
|
66
|
+
agentKind: 'platform' as const,
|
|
67
|
+
actsAsRoleId: 'role.sales-ops',
|
|
68
|
+
sessionCapable: true,
|
|
69
|
+
invocations: [{ kind: 'slash-command' as const, command: '/work handoff' }]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const INTEGRATION_RESOURCE = {
|
|
73
|
+
id: 'attio-crm',
|
|
74
|
+
order: 30,
|
|
75
|
+
kind: 'integration' as const,
|
|
76
|
+
systemPath: 'sys.lead-gen',
|
|
77
|
+
ownerRoleId: 'role.sales-ops',
|
|
78
|
+
status: 'active' as const,
|
|
79
|
+
provider: 'attio'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const SCRIPT_RESOURCE = {
|
|
83
|
+
id: 'leadgen-backfill-script',
|
|
84
|
+
order: 40,
|
|
85
|
+
kind: 'script' as const,
|
|
86
|
+
systemPath: 'sys.lead-gen',
|
|
87
|
+
ownerRoleId: 'role.sales-ops',
|
|
88
|
+
status: 'active' as const,
|
|
89
|
+
language: 'typescript' as const,
|
|
90
|
+
source: {
|
|
91
|
+
file: 'scripts/leadgen/backfill.ts'
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveWithResources(resources: Record<string, unknown>) {
|
|
96
|
+
return resolveOrganizationModel({
|
|
97
|
+
roles: { 'role.sales-ops': VALID_ROLE },
|
|
98
|
+
systems: { 'sys.lead-gen': VALID_SYSTEM },
|
|
99
|
+
resources: resources as Record<string, typeof WORKFLOW_RESOURCE>
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
describe('ResourceEntrySchema', () => {
|
|
104
|
+
it('accepts workflow, agent, integration, and script descriptors with required systemPath', () => {
|
|
105
|
+
expect(ResourceEntrySchema.safeParse(WORKFLOW_RESOURCE).success).toBe(true)
|
|
106
|
+
expect(ResourceEntrySchema.safeParse(AGENT_RESOURCE).success).toBe(true)
|
|
107
|
+
expect(ResourceEntrySchema.safeParse(INTEGRATION_RESOURCE).success).toBe(true)
|
|
108
|
+
expect(ResourceEntrySchema.safeParse(SCRIPT_RESOURCE).success).toBe(true)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('rejects resources without a systemPath', () => {
|
|
112
|
+
const { systemPath: _systemPath, ...resourceWithoutSystem } = WORKFLOW_RESOURCE
|
|
113
|
+
|
|
114
|
+
expect(ResourceEntrySchema.safeParse(resourceWithoutSystem).success).toBe(false)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('defaults codeRefs and accepts resource-level implementation breadcrumbs', () => {
|
|
118
|
+
const withCodeRefs = ResourceEntrySchema.safeParse(WORKFLOW_RESOURCE)
|
|
119
|
+
const withoutCodeRefs = ResourceEntrySchema.safeParse({
|
|
120
|
+
...WORKFLOW_RESOURCE,
|
|
121
|
+
codeRefs: undefined
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
expect(withCodeRefs.success).toBe(true)
|
|
125
|
+
expect(withoutCodeRefs.success).toBe(true)
|
|
126
|
+
|
|
127
|
+
if (withCodeRefs.success) {
|
|
128
|
+
expect(withCodeRefs.data.codeRefs).toEqual(WORKFLOW_RESOURCE.codeRefs)
|
|
129
|
+
}
|
|
130
|
+
if (withoutCodeRefs.success) {
|
|
131
|
+
expect(withoutCodeRefs.data.codeRefs).toEqual([])
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('rejects codeRefs with an empty path', () => {
|
|
136
|
+
expect(
|
|
137
|
+
ResourceEntrySchema.safeParse({
|
|
138
|
+
...WORKFLOW_RESOURCE,
|
|
139
|
+
codeRefs: [
|
|
140
|
+
{
|
|
141
|
+
path: ' ',
|
|
142
|
+
role: 'entrypoint'
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}).success
|
|
146
|
+
).toBe(false)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('rejects codeRefs with an invalid role', () => {
|
|
150
|
+
expect(
|
|
151
|
+
ResourceEntrySchema.safeParse({
|
|
152
|
+
...WORKFLOW_RESOURCE,
|
|
153
|
+
codeRefs: [
|
|
154
|
+
{
|
|
155
|
+
path: 'operations/src/lead-gen/company-scrape/index.ts',
|
|
156
|
+
role: 'runtime'
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}).success
|
|
160
|
+
).toBe(false)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('rejects multi-system membership', () => {
|
|
164
|
+
expect(
|
|
165
|
+
ResourceEntrySchema.safeParse({
|
|
166
|
+
...WORKFLOW_RESOURCE,
|
|
167
|
+
systemPath: ['sys.lead-gen', 'sys.crm']
|
|
168
|
+
}).success
|
|
169
|
+
).toBe(false)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('rejects an agent kind outside the code-side mirror enum', () => {
|
|
173
|
+
expect(
|
|
174
|
+
ResourceEntrySchema.safeParse({
|
|
175
|
+
...AGENT_RESOURCE,
|
|
176
|
+
agentKind: 'runner'
|
|
177
|
+
}).success
|
|
178
|
+
).toBe(false)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('defaults agent invocations and accepts slash-command orchestration entries', () => {
|
|
182
|
+
const result = ResourceEntrySchema.safeParse({
|
|
183
|
+
...AGENT_RESOURCE,
|
|
184
|
+
invocations: undefined
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
expect(result.success).toBe(true)
|
|
188
|
+
if (result.success) {
|
|
189
|
+
expect(result.data.kind).toBe('agent')
|
|
190
|
+
if (result.data.kind === 'agent') {
|
|
191
|
+
expect(result.data.invocations).toEqual([])
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
expect(ResourceEntrySchema.safeParse(AGENT_RESOURCE).success).toBe(true)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('rejects malformed agent invocation entries', () => {
|
|
199
|
+
expect(
|
|
200
|
+
ResourceEntrySchema.safeParse({
|
|
201
|
+
...AGENT_RESOURCE,
|
|
202
|
+
invocations: [{ kind: 'slash-command', command: 'work handoff' }]
|
|
203
|
+
}).success
|
|
204
|
+
).toBe(false)
|
|
205
|
+
})
|
|
206
|
+
|
|
205
207
|
it('accepts event emissions on workflow and agent resources only', () => {
|
|
206
|
-
const workflowResult = ResourceEntrySchema.safeParse({
|
|
207
|
-
...WORKFLOW_RESOURCE,
|
|
208
|
-
emits: [WORKFLOW_EVENT_EMISSION]
|
|
209
|
-
})
|
|
210
|
-
const agentResult = ResourceEntrySchema.safeParse({
|
|
211
|
-
...AGENT_RESOURCE,
|
|
212
|
-
emits: [{ eventKey: 'approved', label: 'Approved' }]
|
|
213
|
-
})
|
|
214
|
-
const integrationResult = ResourceEntrySchema.safeParse({
|
|
215
|
-
...INTEGRATION_RESOURCE,
|
|
216
|
-
emits: [{ eventKey: 'received', label: 'Received' }]
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
expect(workflowResult.success).toBe(true)
|
|
220
|
-
expect(agentResult.success).toBe(true)
|
|
221
|
-
expect(integrationResult.success).toBe(true)
|
|
222
|
-
if (workflowResult.success && workflowResult.data.kind === 'workflow') {
|
|
223
|
-
expect(workflowResult.data.emits).toEqual([WORKFLOW_EVENT_EMISSION])
|
|
224
|
-
}
|
|
225
|
-
if (agentResult.success && agentResult.data.kind === 'agent') {
|
|
226
|
-
expect(agentResult.data.emits).toEqual([{ eventKey: 'approved', label: 'Approved' }])
|
|
227
|
-
}
|
|
228
|
-
if (integrationResult.success && integrationResult.data.kind === 'integration') {
|
|
229
|
-
expect('emits' in integrationResult.data).toBe(false)
|
|
208
|
+
const workflowResult = ResourceEntrySchema.safeParse({
|
|
209
|
+
...WORKFLOW_RESOURCE,
|
|
210
|
+
emits: [WORKFLOW_EVENT_EMISSION]
|
|
211
|
+
})
|
|
212
|
+
const agentResult = ResourceEntrySchema.safeParse({
|
|
213
|
+
...AGENT_RESOURCE,
|
|
214
|
+
emits: [{ eventKey: 'approved', label: 'Approved' }]
|
|
215
|
+
})
|
|
216
|
+
const integrationResult = ResourceEntrySchema.safeParse({
|
|
217
|
+
...INTEGRATION_RESOURCE,
|
|
218
|
+
emits: [{ eventKey: 'received', label: 'Received' }]
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
expect(workflowResult.success).toBe(true)
|
|
222
|
+
expect(agentResult.success).toBe(true)
|
|
223
|
+
expect(integrationResult.success).toBe(true)
|
|
224
|
+
if (workflowResult.success && workflowResult.data.kind === 'workflow') {
|
|
225
|
+
expect(workflowResult.data.emits).toEqual([WORKFLOW_EVENT_EMISSION])
|
|
226
|
+
}
|
|
227
|
+
if (agentResult.success && agentResult.data.kind === 'agent') {
|
|
228
|
+
expect(agentResult.data.emits).toEqual([{ eventKey: 'approved', label: 'Approved' }])
|
|
229
|
+
}
|
|
230
|
+
if (integrationResult.success && integrationResult.data.kind === 'integration') {
|
|
231
|
+
expect('emits' in integrationResult.data).toBe(false)
|
|
230
232
|
}
|
|
231
233
|
})
|
|
232
234
|
|
|
@@ -285,136 +287,281 @@ describe('ResourceEntrySchema', () => {
|
|
|
285
287
|
})
|
|
286
288
|
|
|
287
289
|
it('validates derived event id format', () => {
|
|
288
|
-
expect(EventIdSchema.parse('crm.deal:won')).toBe('crm.deal:won')
|
|
289
|
-
expect(EventIdSchema.parse('LGN-01-company-scrape:processed')).toBe('LGN-01-company-scrape:processed')
|
|
290
|
-
expect(() => EventIdSchema.parse('crm.deal.won')).toThrow()
|
|
291
|
-
})
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
describe('ResourcesDomainSchema', () => {
|
|
295
|
-
it('defaults resources to an empty record when omitted', () => {
|
|
296
|
-
const result = ResourcesDomainSchema.safeParse({})
|
|
297
|
-
|
|
298
|
-
expect(result.success).toBe(true)
|
|
299
|
-
if (result.success) {
|
|
300
|
-
expect(result.data).toEqual(DEFAULT_ORGANIZATION_MODEL_RESOURCES)
|
|
301
|
-
}
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
it.each(['workflow', 'agent', 'integration', 'script'] as const)('accepts kind "%s"', (kind) => {
|
|
305
|
-
expect(ResourceKindSchema.safeParse(kind).success).toBe(true)
|
|
306
|
-
})
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
describe('resolveOrganizationModel - resources domain integration', () => {
|
|
310
|
-
it('accepts valid resources with system and role references', () => {
|
|
311
|
-
const model = resolveWithResources({
|
|
312
|
-
'LGN-01-company-scrape': WORKFLOW_RESOURCE,
|
|
313
|
-
'command-center-assistant': AGENT_RESOURCE,
|
|
314
|
-
'attio-crm': INTEGRATION_RESOURCE,
|
|
315
|
-
'leadgen-backfill-script': SCRIPT_RESOURCE
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
expect(Object.keys(model.resources)).toHaveLength(4)
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
it('throws when a resource entry id does not match its map key', () => {
|
|
322
|
-
expect(() =>
|
|
323
|
-
resolveWithResources({
|
|
324
|
-
'LGN-01-company-scrape': WORKFLOW_RESOURCE,
|
|
325
|
-
'wrong-key': {
|
|
326
|
-
...WORKFLOW_RESOURCE,
|
|
327
|
-
id: 'LGN-01-company-scrape'
|
|
328
|
-
}
|
|
329
|
-
})
|
|
330
|
-
).toThrow()
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
it('throws when systemPath references an unknown system', () => {
|
|
334
|
-
expect(() =>
|
|
335
|
-
resolveWithResources({
|
|
336
|
-
'LGN-01-company-scrape': {
|
|
337
|
-
...WORKFLOW_RESOURCE,
|
|
338
|
-
systemPath: 'sys.missing'
|
|
339
|
-
}
|
|
340
|
-
})
|
|
341
|
-
).toThrow(/unknown system path \\"sys\.missing\\"/)
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
it('reports a dangling resource systemPath on the resource systemPath issue path', () => {
|
|
345
|
-
let error: unknown
|
|
346
|
-
|
|
347
|
-
try {
|
|
348
|
-
resolveWithResources({
|
|
349
|
-
'LGN-01-company-scrape': {
|
|
350
|
-
...WORKFLOW_RESOURCE,
|
|
351
|
-
systemPath: 'sys.missing'
|
|
352
|
-
}
|
|
353
|
-
})
|
|
354
|
-
} catch (caught) {
|
|
355
|
-
error = caught
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
expect(error).toBeInstanceOf(ZodError)
|
|
359
|
-
if (!(error instanceof ZodError)) {
|
|
360
|
-
throw new Error('Expected resolveOrganizationModel to throw a ZodError')
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
expect(error.issues).toEqual(
|
|
364
|
-
expect.arrayContaining([
|
|
365
|
-
expect.objectContaining({
|
|
366
|
-
path: ['resources', 'LGN-01-company-scrape', 'systemPath']
|
|
367
|
-
})
|
|
368
|
-
])
|
|
369
|
-
)
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
it('throws when ownerRoleId references an unknown role', () => {
|
|
373
|
-
expect(() =>
|
|
374
|
-
resolveWithResources({
|
|
375
|
-
'LGN-01-company-scrape': {
|
|
376
|
-
...WORKFLOW_RESOURCE,
|
|
377
|
-
ownerRoleId: 'role.missing'
|
|
378
|
-
}
|
|
379
|
-
})
|
|
380
|
-
).toThrow(/unknown ownerRoleId \\"role\.missing\\"/)
|
|
381
|
-
})
|
|
382
|
-
|
|
383
|
-
it('throws when agent actsAsRoleId references an unknown role', () => {
|
|
384
|
-
expect(() =>
|
|
385
|
-
resolveWithResources({
|
|
386
|
-
'command-center-assistant': {
|
|
387
|
-
...AGENT_RESOURCE,
|
|
388
|
-
actsAsRoleId: 'role.missing'
|
|
389
|
-
}
|
|
390
|
-
})
|
|
391
|
-
).toThrow(/unknown actsAsRoleId \\"role\.missing\\"/)
|
|
392
|
-
})
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
describe('descriptor helper contract', () => {
|
|
396
|
-
it('preserves typed descriptor exports through defineResource and defineResources', () => {
|
|
397
|
-
const workflow = defineResource(WORKFLOW_RESOURCE)
|
|
398
|
-
const resources = defineResources({
|
|
399
|
-
companyScrape: WORKFLOW_RESOURCE,
|
|
400
|
-
commandCenterAssistant: AGENT_RESOURCE
|
|
401
|
-
})
|
|
402
|
-
|
|
403
|
-
expect(workflow.id).toBe('LGN-01-company-scrape')
|
|
404
|
-
expect(resources.commandCenterAssistant.agentKind).toBe('platform')
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
it('derives runtime resourceId and type from the descriptor', () => {
|
|
408
|
-
const bound = bindResourceDescriptor({
|
|
409
|
-
resource: WORKFLOW_RESOURCE,
|
|
410
|
-
name: 'Company Scrape',
|
|
411
|
-
description: 'Scrapes company data for lead generation.',
|
|
412
|
-
version: '1.0.0',
|
|
413
|
-
status: 'prod'
|
|
414
|
-
})
|
|
415
|
-
|
|
416
|
-
expect(bound.resourceId).toBe('LGN-01-company-scrape')
|
|
417
|
-
expect(bound.type).toBe('workflow')
|
|
418
|
-
expect(bound.resource).toBe(WORKFLOW_RESOURCE)
|
|
419
|
-
})
|
|
420
|
-
})
|
|
290
|
+
expect(EventIdSchema.parse('crm.deal:won')).toBe('crm.deal:won')
|
|
291
|
+
expect(EventIdSchema.parse('LGN-01-company-scrape:processed')).toBe('LGN-01-company-scrape:processed')
|
|
292
|
+
expect(() => EventIdSchema.parse('crm.deal.won')).toThrow()
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('ResourcesDomainSchema', () => {
|
|
297
|
+
it('defaults resources to an empty record when omitted', () => {
|
|
298
|
+
const result = ResourcesDomainSchema.safeParse({})
|
|
299
|
+
|
|
300
|
+
expect(result.success).toBe(true)
|
|
301
|
+
if (result.success) {
|
|
302
|
+
expect(result.data).toEqual(DEFAULT_ORGANIZATION_MODEL_RESOURCES)
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it.each(['workflow', 'agent', 'integration', 'script'] as const)('accepts kind "%s"', (kind) => {
|
|
307
|
+
expect(ResourceKindSchema.safeParse(kind).success).toBe(true)
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
describe('resolveOrganizationModel - resources domain integration', () => {
|
|
312
|
+
it('accepts valid resources with system and role references', () => {
|
|
313
|
+
const model = resolveWithResources({
|
|
314
|
+
'LGN-01-company-scrape': WORKFLOW_RESOURCE,
|
|
315
|
+
'command-center-assistant': AGENT_RESOURCE,
|
|
316
|
+
'attio-crm': INTEGRATION_RESOURCE,
|
|
317
|
+
'leadgen-backfill-script': SCRIPT_RESOURCE
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
expect(Object.keys(model.resources)).toHaveLength(4)
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('throws when a resource entry id does not match its map key', () => {
|
|
324
|
+
expect(() =>
|
|
325
|
+
resolveWithResources({
|
|
326
|
+
'LGN-01-company-scrape': WORKFLOW_RESOURCE,
|
|
327
|
+
'wrong-key': {
|
|
328
|
+
...WORKFLOW_RESOURCE,
|
|
329
|
+
id: 'LGN-01-company-scrape'
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
).toThrow()
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('throws when systemPath references an unknown system', () => {
|
|
336
|
+
expect(() =>
|
|
337
|
+
resolveWithResources({
|
|
338
|
+
'LGN-01-company-scrape': {
|
|
339
|
+
...WORKFLOW_RESOURCE,
|
|
340
|
+
systemPath: 'sys.missing'
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
).toThrow(/unknown system path \\"sys\.missing\\"/)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('reports a dangling resource systemPath on the resource systemPath issue path', () => {
|
|
347
|
+
let error: unknown
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
resolveWithResources({
|
|
351
|
+
'LGN-01-company-scrape': {
|
|
352
|
+
...WORKFLOW_RESOURCE,
|
|
353
|
+
systemPath: 'sys.missing'
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
} catch (caught) {
|
|
357
|
+
error = caught
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
expect(error).toBeInstanceOf(ZodError)
|
|
361
|
+
if (!(error instanceof ZodError)) {
|
|
362
|
+
throw new Error('Expected resolveOrganizationModel to throw a ZodError')
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
expect(error.issues).toEqual(
|
|
366
|
+
expect.arrayContaining([
|
|
367
|
+
expect.objectContaining({
|
|
368
|
+
path: ['resources', 'LGN-01-company-scrape', 'systemPath']
|
|
369
|
+
})
|
|
370
|
+
])
|
|
371
|
+
)
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('throws when ownerRoleId references an unknown role', () => {
|
|
375
|
+
expect(() =>
|
|
376
|
+
resolveWithResources({
|
|
377
|
+
'LGN-01-company-scrape': {
|
|
378
|
+
...WORKFLOW_RESOURCE,
|
|
379
|
+
ownerRoleId: 'role.missing'
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
).toThrow(/unknown ownerRoleId \\"role\.missing\\"/)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it('throws when agent actsAsRoleId references an unknown role', () => {
|
|
386
|
+
expect(() =>
|
|
387
|
+
resolveWithResources({
|
|
388
|
+
'command-center-assistant': {
|
|
389
|
+
...AGENT_RESOURCE,
|
|
390
|
+
actsAsRoleId: 'role.missing'
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
).toThrow(/unknown actsAsRoleId \\"role\.missing\\"/)
|
|
394
|
+
})
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
describe('descriptor helper contract', () => {
|
|
398
|
+
it('preserves typed descriptor exports through defineResource and defineResources', () => {
|
|
399
|
+
const workflow = defineResource(WORKFLOW_RESOURCE)
|
|
400
|
+
const resources = defineResources({
|
|
401
|
+
companyScrape: WORKFLOW_RESOURCE,
|
|
402
|
+
commandCenterAssistant: AGENT_RESOURCE
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
expect(workflow.id).toBe('LGN-01-company-scrape')
|
|
406
|
+
expect(resources.commandCenterAssistant.agentKind).toBe('platform')
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('derives runtime resourceId and type from the descriptor', () => {
|
|
410
|
+
const bound = bindResourceDescriptor({
|
|
411
|
+
resource: WORKFLOW_RESOURCE,
|
|
412
|
+
name: 'Company Scrape',
|
|
413
|
+
description: 'Scrapes company data for lead generation.',
|
|
414
|
+
version: '1.0.0',
|
|
415
|
+
status: 'prod'
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
expect(bound.resourceId).toBe('LGN-01-company-scrape')
|
|
419
|
+
expect(bound.type).toBe('workflow')
|
|
420
|
+
expect(bound.resource).toBe(WORKFLOW_RESOURCE)
|
|
421
|
+
})
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
describe('ContractRefSchema — ref string shape', () => {
|
|
425
|
+
it('accepts valid package/subpath#ExportName refs', () => {
|
|
426
|
+
expect(ContractRefSchema.safeParse('@repo/elevasis-core/contracts/apollo-import#inputSchema').success).toBe(true)
|
|
427
|
+
expect(
|
|
428
|
+
ContractRefSchema.safeParse('@repo/elevasis-core/contracts/dtc-subscription-score#outputSchema').success
|
|
429
|
+
).toBe(true)
|
|
430
|
+
expect(ContractRefSchema.safeParse('my-package/sub/path#MySchema').success).toBe(true)
|
|
431
|
+
expect(ContractRefSchema.safeParse('pkg#schema').success).toBe(true)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
it('rejects refs without a #ExportName suffix', () => {
|
|
435
|
+
expect(ContractRefSchema.safeParse('@repo/elevasis-core/contracts/apollo-import').success).toBe(false)
|
|
436
|
+
expect(ContractRefSchema.safeParse('pkg/sub').success).toBe(false)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
it('rejects refs with an empty export name after #', () => {
|
|
440
|
+
expect(ContractRefSchema.safeParse('@repo/elevasis-core/contracts/apollo-import#').success).toBe(false)
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('rejects empty strings', () => {
|
|
444
|
+
expect(ContractRefSchema.safeParse('').success).toBe(false)
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
describe('parseContractRef — browser-safe parser', () => {
|
|
449
|
+
it('parses a valid ref into moduleSpecifier and exportName', () => {
|
|
450
|
+
const result = parseContractRef('@repo/elevasis-core/contracts/apollo-import#inputSchema')
|
|
451
|
+
expect(result.moduleSpecifier).toBe('@repo/elevasis-core/contracts/apollo-import')
|
|
452
|
+
expect(result.exportName).toBe('inputSchema')
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('uses lastIndexOf so a # in the module specifier (unusual but valid) is handled correctly', () => {
|
|
456
|
+
const result = parseContractRef('pkg/sub#Export#actualExport')
|
|
457
|
+
expect(result.moduleSpecifier).toBe('pkg/sub#Export')
|
|
458
|
+
expect(result.exportName).toBe('actualExport')
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
it('throws on a ref without #', () => {
|
|
462
|
+
expect(() => parseContractRef('pkg/no-hash')).toThrow(/missing the required/)
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
it('throws on a ref with empty export name', () => {
|
|
466
|
+
expect(() => parseContractRef('pkg/sub#')).toThrow(/empty export name/)
|
|
467
|
+
})
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
describe('ResourceOntologyBindingSchema — contract field', () => {
|
|
471
|
+
it('accepts a binding without a contract field (backward compat)', () => {
|
|
472
|
+
expect(
|
|
473
|
+
ResourceOntologyBindingSchema.safeParse({
|
|
474
|
+
actions: ['sys.lead-gen:action/company.scrape']
|
|
475
|
+
}).success
|
|
476
|
+
).toBe(true)
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
it('accepts a binding with a valid contract.input ref', () => {
|
|
480
|
+
expect(
|
|
481
|
+
ResourceOntologyBindingSchema.safeParse({
|
|
482
|
+
contract: {
|
|
483
|
+
input: '@repo/elevasis-core/contracts/apollo-import#inputSchema'
|
|
484
|
+
}
|
|
485
|
+
}).success
|
|
486
|
+
).toBe(true)
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it('accepts a binding with both contract.input and contract.output refs', () => {
|
|
490
|
+
expect(
|
|
491
|
+
ResourceOntologyBindingSchema.safeParse({
|
|
492
|
+
contract: {
|
|
493
|
+
input: '@repo/elevasis-core/contracts/dtc-subscription-score#inputSchema',
|
|
494
|
+
output: '@repo/elevasis-core/contracts/dtc-subscription-score#outputSchema'
|
|
495
|
+
}
|
|
496
|
+
}).success
|
|
497
|
+
).toBe(true)
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
it('accepts a binding with an empty contract object (both sides optional)', () => {
|
|
501
|
+
expect(
|
|
502
|
+
ResourceOntologyBindingSchema.safeParse({
|
|
503
|
+
contract: {}
|
|
504
|
+
}).success
|
|
505
|
+
).toBe(true)
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
it('rejects a malformed contract.input ref (missing #ExportName)', () => {
|
|
509
|
+
expect(
|
|
510
|
+
ResourceOntologyBindingSchema.safeParse({
|
|
511
|
+
contract: {
|
|
512
|
+
input: '@repo/elevasis-core/contracts/apollo-import'
|
|
513
|
+
}
|
|
514
|
+
}).success
|
|
515
|
+
).toBe(false)
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
it('rejects a malformed contract.output ref (missing #ExportName)', () => {
|
|
519
|
+
expect(
|
|
520
|
+
ResourceOntologyBindingSchema.safeParse({
|
|
521
|
+
contract: {
|
|
522
|
+
output: 'not-a-valid-ref'
|
|
523
|
+
}
|
|
524
|
+
}).success
|
|
525
|
+
).toBe(false)
|
|
526
|
+
})
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
describe('resolveOrganizationModel — contract ref Tier-1 shape validation', () => {
|
|
530
|
+
it('accepts a resource with a valid contract binding', () => {
|
|
531
|
+
const model = resolveWithResources({
|
|
532
|
+
'LGN-01-company-scrape': {
|
|
533
|
+
...WORKFLOW_RESOURCE,
|
|
534
|
+
ontology: {
|
|
535
|
+
contract: {
|
|
536
|
+
input: '@repo/elevasis-core/contracts/apollo-import#inputSchema'
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
})
|
|
541
|
+
expect(model.resources['LGN-01-company-scrape']?.ontology?.contract?.input).toBe(
|
|
542
|
+
'@repo/elevasis-core/contracts/apollo-import#inputSchema'
|
|
543
|
+
)
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
it('rejects a resource with a malformed contract.input ref', () => {
|
|
547
|
+
expect(() =>
|
|
548
|
+
resolveWithResources({
|
|
549
|
+
'LGN-01-company-scrape': {
|
|
550
|
+
...WORKFLOW_RESOURCE,
|
|
551
|
+
ontology: {
|
|
552
|
+
contract: {
|
|
553
|
+
input: 'bad-ref-no-hash'
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
})
|
|
558
|
+
).toThrow()
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
it('leaves resources without contract binding unchanged (additive + optional)', () => {
|
|
562
|
+
const model = resolveWithResources({
|
|
563
|
+
'LGN-01-company-scrape': WORKFLOW_RESOURCE
|
|
564
|
+
})
|
|
565
|
+
expect(model.resources['LGN-01-company-scrape']?.ontology).toBeUndefined()
|
|
566
|
+
})
|
|
567
|
+
})
|