@elevasis/core 0.20.0 → 0.22.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 (77) hide show
  1. package/dist/index.d.ts +524 -6
  2. package/dist/index.js +417 -42
  3. package/dist/knowledge/index.d.ts +151 -1
  4. package/dist/organization-model/index.d.ts +524 -6
  5. package/dist/organization-model/index.js +417 -42
  6. package/dist/test-utils/index.d.ts +270 -1
  7. package/dist/test-utils/index.js +407 -41
  8. package/package.json +5 -5
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +501 -303
  10. package/src/auth/multi-tenancy/permissions.ts +20 -8
  11. package/src/business/README.md +2 -2
  12. package/src/business/acquisition/api-schemas.test.ts +198 -0
  13. package/src/business/acquisition/api-schemas.ts +250 -9
  14. package/src/business/acquisition/build-templates.test.ts +28 -0
  15. package/src/business/acquisition/build-templates.ts +20 -8
  16. package/src/business/acquisition/index.ts +12 -0
  17. package/src/business/acquisition/types.ts +6 -1
  18. package/src/business/clients/api-schemas.test.ts +115 -0
  19. package/src/business/clients/api-schemas.ts +158 -0
  20. package/src/business/clients/index.ts +1 -0
  21. package/src/business/deals/api-schemas.ts +8 -0
  22. package/src/business/index.ts +5 -2
  23. package/src/business/projects/types.ts +19 -0
  24. package/src/execution/engine/__tests__/fixtures/test-agents.ts +10 -8
  25. package/src/execution/engine/agent/core/__tests__/agent.test.ts +16 -12
  26. package/src/execution/engine/agent/core/__tests__/error-passthrough.test.ts +4 -3
  27. package/src/execution/engine/agent/core/types.ts +25 -15
  28. package/src/execution/engine/agent/index.ts +6 -4
  29. package/src/execution/engine/agent/reasoning/__tests__/request-builder.test.ts +24 -18
  30. package/src/execution/engine/index.ts +3 -0
  31. package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.test.ts +55 -0
  32. package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +107 -41
  33. package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.test.ts +48 -0
  34. package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.ts +99 -0
  35. package/src/execution/engine/tools/integration/server/adapters/apollo/index.ts +1 -0
  36. package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.test.ts +18 -0
  37. package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.ts +194 -0
  38. package/src/execution/engine/tools/integration/server/adapters/clickup/index.ts +7 -0
  39. package/src/execution/engine/workflow/types.ts +7 -0
  40. package/src/integrations/credentials/api-schemas.ts +21 -2
  41. package/src/integrations/credentials/schemas.ts +200 -164
  42. package/src/organization-model/README.md +10 -3
  43. package/src/organization-model/__tests__/defaults.test.ts +6 -0
  44. package/src/organization-model/__tests__/domains/resources.test.ts +188 -0
  45. package/src/organization-model/__tests__/domains/roles.test.ts +402 -347
  46. package/src/organization-model/__tests__/domains/systems.test.ts +193 -0
  47. package/src/organization-model/__tests__/knowledge.test.ts +39 -0
  48. package/src/organization-model/__tests__/prospecting-ssot.test.ts +7 -4
  49. package/src/organization-model/__tests__/resolve.test.ts +1 -1
  50. package/src/organization-model/defaults.ts +24 -3
  51. package/src/organization-model/domains/knowledge.ts +3 -2
  52. package/src/organization-model/domains/prospecting.ts +182 -25
  53. package/src/organization-model/domains/resources.ts +88 -0
  54. package/src/organization-model/domains/roles.ts +93 -55
  55. package/src/organization-model/domains/sales.ts +24 -3
  56. package/src/organization-model/domains/systems.ts +46 -0
  57. package/src/organization-model/icons.ts +1 -0
  58. package/src/organization-model/index.ts +2 -0
  59. package/src/organization-model/organization-model.mdx +33 -14
  60. package/src/organization-model/published.ts +52 -1
  61. package/src/organization-model/schema.ts +121 -0
  62. package/src/organization-model/types.ts +46 -1
  63. package/src/platform/api/types.ts +38 -35
  64. package/src/platform/constants/versions.ts +1 -1
  65. package/src/platform/registry/__tests__/resource-registry.test.ts +2051 -2005
  66. package/src/platform/registry/__tests__/validation.test.ts +1343 -1086
  67. package/src/platform/registry/index.ts +14 -0
  68. package/src/platform/registry/resource-registry.ts +40 -2
  69. package/src/platform/registry/serialization.ts +241 -202
  70. package/src/platform/registry/serialized-types.ts +1 -0
  71. package/src/platform/registry/types.ts +411 -361
  72. package/src/platform/registry/validation.ts +743 -513
  73. package/src/projects/api-schemas.ts +290 -267
  74. package/src/reference/_generated/contracts.md +501 -303
  75. package/src/reference/glossary.md +8 -3
  76. package/src/server.ts +2 -0
  77. package/src/supabase/database.types.ts +121 -0
@@ -1,164 +1,200 @@
1
- import { z } from 'zod'
2
-
3
- /**
4
- * Credential field definition
5
- */
6
- export interface CredentialField {
7
- key: string
8
- label: string
9
- type: 'password' | 'text'
10
- required: boolean
11
- placeholder?: string
12
- description?: string
13
- }
14
-
15
- /**
16
- * Credential schema definition
17
- */
18
- export interface CredentialSchema {
19
- type: string
20
- label: string
21
- description: string
22
- fields?: CredentialField[]
23
- docsUrl?: string
24
- nameSuggestions: string[]
25
- oauthProvider?: string // For OAuth types
26
- }
27
-
28
- /**
29
- * Zod schemas for runtime validation
30
- */
31
- const CredentialFieldSchema = z.object({
32
- key: z.string(),
33
- label: z.string(),
34
- type: z.enum(['password', 'text']),
35
- required: z.boolean(),
36
- placeholder: z.string().optional(),
37
- description: z.string().optional()
38
- })
39
-
40
- const CredentialSchemaZod = z.object({
41
- type: z.string(),
42
- label: z.string(),
43
- description: z.string(),
44
- fields: z.array(CredentialFieldSchema).optional(),
45
- docsUrl: z.string().url().optional(),
46
- nameSuggestions: z.array(z.string()),
47
- oauthProvider: z.string().optional()
48
- })
49
-
50
- /**
51
- * Credential Schema Registry
52
- *
53
- * Add new integration = add schema object here (no modal changes needed)
54
- *
55
- * Schema types:
56
- * - oauth: OAuth-based authentication (handled via OAuth flow)
57
- * - single_field: Single API key field (generic)
58
- */
59
- export const CREDENTIAL_SCHEMAS: Record<string, CredentialSchema> = {
60
- 'google-sheets': {
61
- type: 'oauth',
62
- label: 'Google Sheets',
63
- description: 'Google Sheets OAuth integration',
64
- oauthProvider: 'google-sheets',
65
- nameSuggestions: ['google-sheets-prod', 'google-sheets-dev', 'sheets-workspace']
66
- },
67
-
68
- dropbox: {
69
- type: 'oauth',
70
- label: 'Dropbox',
71
- description: 'Dropbox OAuth integration',
72
- oauthProvider: 'dropbox',
73
- nameSuggestions: ['dropbox-prod', 'dropbox-dev', 'elevasis-dropbox']
74
- },
75
-
76
- oauth: {
77
- type: 'oauth',
78
- label: 'OAuth',
79
- description: 'Generic OAuth credential',
80
- nameSuggestions: []
81
- },
82
-
83
- 'api-key': {
84
- type: 'single_field',
85
- label: 'API Key',
86
- description: 'Single-field API key credential',
87
- fields: [
88
- {
89
- key: 'apiKey',
90
- label: 'API Key',
91
- type: 'password',
92
- required: true,
93
- placeholder: 'Enter your API key',
94
- description: 'API key for the service'
95
- }
96
- ],
97
- nameSuggestions: ['service-prod', 'service-dev', 'api-key']
98
- },
99
-
100
- 'webhook-secret': {
101
- type: 'single_field',
102
- label: 'Webhook Secret',
103
- description: 'Webhook signing secret for signature validation',
104
- fields: [
105
- {
106
- key: 'signingSecret',
107
- label: 'Signing Secret',
108
- type: 'password',
109
- required: true,
110
- placeholder: 'whsec_...',
111
- description: 'Webhook signing secret from provider dashboard'
112
- }
113
- ],
114
- nameSuggestions: ['my-org-cal-com-webhook', 'my-org-stripe-webhook', 'my-org-signature-api-webhook']
115
- },
116
-
117
- 'api-key-secret': {
118
- type: 'api-key-secret',
119
- label: 'API Key + Secret',
120
- description: 'API key and secret pair authentication',
121
- fields: [
122
- {
123
- key: 'apiKey',
124
- label: 'API Key',
125
- type: 'password',
126
- required: true
127
- },
128
- {
129
- key: 'apiSecret',
130
- label: 'API Secret',
131
- type: 'password',
132
- required: true
133
- }
134
- ],
135
- nameSuggestions: []
136
- }
137
- }
138
-
139
- /**
140
- * Get credential schema by type
141
- * @param type - Credential type identifier
142
- * @returns Validated credential schema
143
- * @throws ZodError if schema is malformed
144
- */
145
- export function getCredentialSchema(type: string): CredentialSchema {
146
- const schema = CREDENTIAL_SCHEMAS[type] || CREDENTIAL_SCHEMAS['api-key']
147
- return CredentialSchemaZod.parse(schema)
148
- }
149
-
150
- /**
151
- * List all available credential types (for frontend dropdown)
152
- * @returns Array of credential schemas
153
- */
154
- export function listCredentialSchemas(): CredentialSchema[] {
155
- return Object.values(CREDENTIAL_SCHEMAS)
156
- }
157
-
158
- /**
159
- * Get all credential type identifiers
160
- * @returns Array of type strings
161
- */
162
- export function getCredentialTypes(): string[] {
163
- return Object.keys(CREDENTIAL_SCHEMAS)
164
- }
1
+ import { z } from 'zod'
2
+
3
+ /**
4
+ * Credential field definition
5
+ */
6
+ export interface CredentialField {
7
+ key: string
8
+ label: string
9
+ type: 'password' | 'text'
10
+ required: boolean
11
+ placeholder?: string
12
+ description?: string
13
+ }
14
+
15
+ /**
16
+ * Credential schema definition
17
+ */
18
+ export interface CredentialSchema {
19
+ type: string
20
+ label: string
21
+ description: string
22
+ fields?: CredentialField[]
23
+ docsUrl?: string
24
+ nameSuggestions: string[]
25
+ oauthProvider?: string // For OAuth types
26
+ }
27
+
28
+ /**
29
+ * Zod schemas for runtime validation
30
+ */
31
+ const CredentialFieldSchema = z.object({
32
+ key: z.string(),
33
+ label: z.string(),
34
+ type: z.enum(['password', 'text']),
35
+ required: z.boolean(),
36
+ placeholder: z.string().optional(),
37
+ description: z.string().optional()
38
+ })
39
+
40
+ const CredentialSchemaZod = z.object({
41
+ type: z.string(),
42
+ label: z.string(),
43
+ description: z.string(),
44
+ fields: z.array(CredentialFieldSchema).optional(),
45
+ docsUrl: z.string().url().optional(),
46
+ nameSuggestions: z.array(z.string()),
47
+ oauthProvider: z.string().optional()
48
+ })
49
+
50
+ /**
51
+ * Credential Schema Registry
52
+ *
53
+ * Add new integration = add schema object here (no modal changes needed)
54
+ *
55
+ * Schema types:
56
+ * - oauth: OAuth-based authentication (handled via OAuth flow)
57
+ * - single_field: Single API key field (generic)
58
+ */
59
+ export const CREDENTIAL_SCHEMAS: Record<string, CredentialSchema> = {
60
+ 'google-sheets': {
61
+ type: 'oauth',
62
+ label: 'Google Sheets',
63
+ description: 'Google Sheets OAuth integration',
64
+ oauthProvider: 'google-sheets',
65
+ nameSuggestions: ['google-sheets-prod', 'google-sheets-dev', 'sheets-workspace']
66
+ },
67
+
68
+ dropbox: {
69
+ type: 'oauth',
70
+ label: 'Dropbox',
71
+ description: 'Dropbox OAuth integration',
72
+ oauthProvider: 'dropbox',
73
+ nameSuggestions: ['dropbox-prod', 'dropbox-dev', 'elevasis-dropbox']
74
+ },
75
+
76
+ oauth: {
77
+ type: 'oauth',
78
+ label: 'OAuth',
79
+ description: 'Generic OAuth credential',
80
+ nameSuggestions: []
81
+ },
82
+
83
+ 'api-key': {
84
+ type: 'single_field',
85
+ label: 'API Key',
86
+ description: 'Single-field API key credential',
87
+ fields: [
88
+ {
89
+ key: 'apiKey',
90
+ label: 'API Key',
91
+ type: 'password',
92
+ required: true,
93
+ placeholder: 'Enter your API key',
94
+ description: 'API key for the service'
95
+ }
96
+ ],
97
+ nameSuggestions: ['service-prod', 'service-dev', 'api-key']
98
+ },
99
+
100
+ 'webhook-secret': {
101
+ type: 'single_field',
102
+ label: 'Webhook Secret',
103
+ description: 'Webhook signing secret for signature validation',
104
+ fields: [
105
+ {
106
+ key: 'signingSecret',
107
+ label: 'Signing Secret',
108
+ type: 'password',
109
+ required: true,
110
+ placeholder: 'whsec_...',
111
+ description: 'Webhook signing secret from provider dashboard'
112
+ }
113
+ ],
114
+ nameSuggestions: ['my-org-cal-com-webhook', 'my-org-stripe-webhook', 'my-org-signature-api-webhook']
115
+ },
116
+
117
+ 'api-key-secret': {
118
+ type: 'api-key-secret',
119
+ label: 'API Key + Secret',
120
+ description: 'API key and secret pair authentication',
121
+ fields: [
122
+ {
123
+ key: 'apiKey',
124
+ label: 'API Key',
125
+ type: 'password',
126
+ required: true
127
+ },
128
+ {
129
+ key: 'apiSecret',
130
+ label: 'API Secret',
131
+ type: 'password',
132
+ required: true
133
+ }
134
+ ],
135
+ nameSuggestions: []
136
+ },
137
+
138
+ apify: {
139
+ type: 'single_field',
140
+ label: 'Apify',
141
+ description: 'Apify API token for running web-scraping actors (e.g. website crawl, Google Places scrape).',
142
+ fields: [
143
+ {
144
+ key: 'apiKey',
145
+ label: 'API Token',
146
+ type: 'password',
147
+ required: true,
148
+ placeholder: 'apify_api_...',
149
+ description: 'Personal API token from https://console.apify.com/account/integrations'
150
+ }
151
+ ],
152
+ docsUrl: 'https://docs.apify.com/platform/integrations/api',
153
+ nameSuggestions: ['elevasis-apify', 'apify-prod', 'apify-dev']
154
+ },
155
+
156
+ clickup: {
157
+ type: 'single_field',
158
+ label: 'ClickUp',
159
+ description: 'ClickUp personal API token for creating tasks in tenant-managed ClickUp workspaces.',
160
+ fields: [
161
+ {
162
+ key: 'apiToken',
163
+ label: 'API Token',
164
+ type: 'password',
165
+ required: true,
166
+ placeholder: 'pk_...',
167
+ description: 'Personal API token from ClickUp settings. Personal tokens start with pk_.'
168
+ }
169
+ ],
170
+ docsUrl: 'https://developer.clickup.com/docs/authentication',
171
+ nameSuggestions: ['clickup-demo', 'clickup-prod', 'clickup-dev']
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Get credential schema by type
177
+ * @param type - Credential type identifier
178
+ * @returns Validated credential schema
179
+ * @throws ZodError if schema is malformed
180
+ */
181
+ export function getCredentialSchema(type: string): CredentialSchema {
182
+ const schema = CREDENTIAL_SCHEMAS[type] || CREDENTIAL_SCHEMAS['api-key']
183
+ return CredentialSchemaZod.parse(schema)
184
+ }
185
+
186
+ /**
187
+ * List all available credential types (for frontend dropdown)
188
+ * @returns Array of credential schemas
189
+ */
190
+ export function listCredentialSchemas(): CredentialSchema[] {
191
+ return Object.values(CREDENTIAL_SCHEMAS)
192
+ }
193
+
194
+ /**
195
+ * Get all credential type identifiers
196
+ * @returns Array of type strings
197
+ */
198
+ export function getCredentialTypes(): string[] {
199
+ return Object.keys(CREDENTIAL_SCHEMAS)
200
+ }
@@ -47,10 +47,13 @@ Top-level fields:
47
47
  - `offerings`
48
48
  - `roles`
49
49
  - `goals`
50
+ - `systems`
51
+ - `resources`
50
52
  - `statuses`
51
53
  - `operations`
54
+ - `knowledge`
52
55
 
53
- Resources bind to the graph from deployment metadata through `links` and `category`.
56
+ Resource identity is authored in `resources.entries`. Runtime workflows, agents, and integrations import those descriptors, derive `resourceId` and kind from them, and attach executable behavior in operations code.
54
57
 
55
58
  ## Feature Set
56
59
 
@@ -77,6 +80,10 @@ Cross-collection links use kind-prefixed IDs:
77
80
  - `resource:lead-import`
78
81
  - `capability:operations.queue.review`
79
82
 
83
+ ## Resource Descriptors
84
+
85
+ The OM Resources domain is governance-only. Descriptors declare canonical `id`, required `systemId`, governance `status`, and optional role ownership. `DeploymentSpec` remains the runtime/deploy assembly around those descriptors, not a second resource identity catalog.
86
+
80
87
  ## Resolution Semantics
81
88
 
82
89
  - `defineOrganizationModel()` is a typed helper.
@@ -92,11 +99,11 @@ Cross-collection links use kind-prefixed IDs:
92
99
  - Child feature IDs require ancestor feature nodes.
93
100
  - Container features omit `path`.
94
101
  - Leaf features provide `path`.
95
- - Resource links are validated against graph node IDs during registry registration.
102
+ - Systems, resources, roles, knowledge nodes, and goals must resolve their declared cross-references.
96
103
 
97
104
  ## Practical Guidance
98
105
 
99
106
  - Use `resolveOrganizationModel()` when you need a runtime-safe model.
100
107
  - Use `defineOrganizationModel()` when authoring static overrides.
101
108
  - Keep feature IDs stable because shell routing, gating, breadcrumbs, and docs depend on them.
102
- - Put semantic resource relationships on resource metadata, not on feature nodes.
109
+ - Put resource identity and governance in `resources.entries`; attach executable behavior in operations.
@@ -14,6 +14,7 @@ import { DEFAULT_ORGANIZATION_MODEL_OPERATIONS } from '../domains/operations'
14
14
  import { OperationsDomainSchema } from '../domains/operations'
15
15
  import { DEFAULT_ORGANIZATION_MODEL_STATUSES } from '../domains/statuses'
16
16
  import { StatusesDomainSchema } from '../domains/statuses'
17
+ import { DEFAULT_ORGANIZATION_MODEL_SYSTEMS, SystemsDomainSchema } from '../domains/systems'
17
18
  import { resolveOrganizationModel } from '../resolve'
18
19
  import { OrganizationModelSchema } from '../schema'
19
20
 
@@ -55,6 +56,11 @@ const domainCases = [
55
56
  name: 'DEFAULT_ORGANIZATION_MODEL_OPERATIONS',
56
57
  constant: DEFAULT_ORGANIZATION_MODEL_OPERATIONS,
57
58
  schema: OperationsDomainSchema
59
+ },
60
+ {
61
+ name: 'DEFAULT_ORGANIZATION_MODEL_SYSTEMS',
62
+ constant: DEFAULT_ORGANIZATION_MODEL_SYSTEMS,
63
+ schema: SystemsDomainSchema
58
64
  }
59
65
  ] as const
60
66
 
@@ -0,0 +1,188 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { bindResourceDescriptor } from '../../../platform/registry/types'
3
+ import {
4
+ DEFAULT_ORGANIZATION_MODEL_RESOURCES,
5
+ ResourceEntrySchema,
6
+ ResourceKindSchema,
7
+ ResourcesDomainSchema,
8
+ defineResource,
9
+ defineResources
10
+ } from '../../domains/resources'
11
+ import { resolveOrganizationModel } from '../../resolve'
12
+
13
+ const VALID_SYSTEM = {
14
+ id: 'sys.lead-gen',
15
+ title: 'Lead Generation Pipeline',
16
+ description: 'Coordinates prospecting, enrichment, qualification, and outreach preparation.',
17
+ kind: 'operational' as const,
18
+ status: 'active' as const
19
+ }
20
+
21
+ const VALID_ROLE = {
22
+ id: 'role.sales-ops',
23
+ title: 'Sales Ops'
24
+ }
25
+
26
+ const WORKFLOW_RESOURCE = {
27
+ id: 'LGN-01-company-scrape',
28
+ kind: 'workflow' as const,
29
+ systemId: 'sys.lead-gen',
30
+ ownerRoleId: 'role.sales-ops',
31
+ status: 'active' as const,
32
+ capabilityKey: 'lead-gen.company.scrape'
33
+ }
34
+
35
+ const AGENT_RESOURCE = {
36
+ id: 'command-center-assistant',
37
+ kind: 'agent' as const,
38
+ systemId: 'sys.lead-gen',
39
+ ownerRoleId: 'role.sales-ops',
40
+ status: 'active' as const,
41
+ agentKind: 'system' as const,
42
+ actsAsRoleId: 'role.sales-ops',
43
+ sessionCapable: true
44
+ }
45
+
46
+ const INTEGRATION_RESOURCE = {
47
+ id: 'attio-crm',
48
+ kind: 'integration' as const,
49
+ systemId: 'sys.lead-gen',
50
+ ownerRoleId: 'role.sales-ops',
51
+ status: 'active' as const,
52
+ provider: 'attio'
53
+ }
54
+
55
+ function resolveWithResources(resources: unknown[]) {
56
+ return resolveOrganizationModel({
57
+ roles: { roles: [VALID_ROLE] },
58
+ systems: { systems: [VALID_SYSTEM] },
59
+ resources: { entries: resources }
60
+ })
61
+ }
62
+
63
+ describe('ResourceEntrySchema', () => {
64
+ it('accepts workflow, agent, and integration descriptors with required systemId', () => {
65
+ expect(ResourceEntrySchema.safeParse(WORKFLOW_RESOURCE).success).toBe(true)
66
+ expect(ResourceEntrySchema.safeParse(AGENT_RESOURCE).success).toBe(true)
67
+ expect(ResourceEntrySchema.safeParse(INTEGRATION_RESOURCE).success).toBe(true)
68
+ })
69
+
70
+ it('rejects resources without a systemId', () => {
71
+ const { systemId: _systemId, ...resourceWithoutSystem } = WORKFLOW_RESOURCE
72
+
73
+ expect(ResourceEntrySchema.safeParse(resourceWithoutSystem).success).toBe(false)
74
+ })
75
+
76
+ it('rejects multi-system membership', () => {
77
+ expect(
78
+ ResourceEntrySchema.safeParse({
79
+ ...WORKFLOW_RESOURCE,
80
+ systemId: ['sys.lead-gen', 'sys.crm']
81
+ }).success
82
+ ).toBe(false)
83
+ })
84
+
85
+ it('rejects an agent kind outside the code-side mirror enum', () => {
86
+ expect(
87
+ ResourceEntrySchema.safeParse({
88
+ ...AGENT_RESOURCE,
89
+ agentKind: 'runner'
90
+ }).success
91
+ ).toBe(false)
92
+ })
93
+ })
94
+
95
+ describe('ResourcesDomainSchema', () => {
96
+ it('defaults resources to an empty array when omitted', () => {
97
+ const result = ResourcesDomainSchema.safeParse({})
98
+
99
+ expect(result.success).toBe(true)
100
+ if (result.success) {
101
+ expect(result.data).toEqual(DEFAULT_ORGANIZATION_MODEL_RESOURCES)
102
+ }
103
+ })
104
+
105
+ it.each(['workflow', 'agent', 'integration'] as const)('accepts kind "%s"', (kind) => {
106
+ expect(ResourceKindSchema.safeParse(kind).success).toBe(true)
107
+ })
108
+ })
109
+
110
+ describe('resolveOrganizationModel - resources domain integration', () => {
111
+ it('accepts valid resources with system and role references', () => {
112
+ const model = resolveWithResources([WORKFLOW_RESOURCE, AGENT_RESOURCE, INTEGRATION_RESOURCE])
113
+
114
+ expect(model.resources.entries).toHaveLength(3)
115
+ })
116
+
117
+ it('throws when resource IDs are duplicated', () => {
118
+ expect(() =>
119
+ resolveWithResources([
120
+ WORKFLOW_RESOURCE,
121
+ {
122
+ ...WORKFLOW_RESOURCE,
123
+ capabilityKey: 'lead-gen.company.scrape-duplicate'
124
+ }
125
+ ])
126
+ ).toThrow(/Resource id \\"LGN-01-company-scrape\\" must be unique/)
127
+ })
128
+
129
+ it('throws when systemId references an unknown system', () => {
130
+ expect(() =>
131
+ resolveWithResources([
132
+ {
133
+ ...WORKFLOW_RESOURCE,
134
+ systemId: 'sys.missing'
135
+ }
136
+ ])
137
+ ).toThrow(/unknown systemId \\"sys\.missing\\"/)
138
+ })
139
+
140
+ it('throws when ownerRoleId references an unknown role', () => {
141
+ expect(() =>
142
+ resolveWithResources([
143
+ {
144
+ ...WORKFLOW_RESOURCE,
145
+ ownerRoleId: 'role.missing'
146
+ }
147
+ ])
148
+ ).toThrow(/unknown ownerRoleId \\"role\.missing\\"/)
149
+ })
150
+
151
+ it('throws when agent actsAsRoleId references an unknown role', () => {
152
+ expect(() =>
153
+ resolveWithResources([
154
+ {
155
+ ...AGENT_RESOURCE,
156
+ actsAsRoleId: 'role.missing'
157
+ }
158
+ ])
159
+ ).toThrow(/unknown actsAsRoleId \\"role\.missing\\"/)
160
+ })
161
+ })
162
+
163
+ describe('descriptor helper contract', () => {
164
+ it('preserves typed descriptor exports through defineResource and defineResources', () => {
165
+ const workflow = defineResource(WORKFLOW_RESOURCE)
166
+ const resources = defineResources({
167
+ companyScrape: WORKFLOW_RESOURCE,
168
+ commandCenterAssistant: AGENT_RESOURCE
169
+ })
170
+
171
+ expect(workflow.id).toBe('LGN-01-company-scrape')
172
+ expect(resources.commandCenterAssistant.agentKind).toBe('system')
173
+ })
174
+
175
+ it('derives runtime resourceId and type from the descriptor', () => {
176
+ const bound = bindResourceDescriptor({
177
+ resource: WORKFLOW_RESOURCE,
178
+ name: 'Company Scrape',
179
+ description: 'Scrapes company data for lead generation.',
180
+ version: '1.0.0',
181
+ status: 'prod'
182
+ })
183
+
184
+ expect(bound.resourceId).toBe('LGN-01-company-scrape')
185
+ expect(bound.type).toBe('workflow')
186
+ expect(bound.resource).toBe(WORKFLOW_RESOURCE)
187
+ })
188
+ })