@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
@@ -4,7 +4,36 @@ import { DescriptionSchema, DisplayMetadataSchema, ModelIdSchema } from './share
4
4
 
5
5
  export const ProspectingLifecycleStageSchema = DisplayMetadataSchema.extend({
6
6
  id: ModelIdSchema,
7
- order: z.number().int().min(0)
7
+ order: z.number().min(0)
8
+ })
9
+
10
+ export const RecordColumnConfigSchema = z.object({
11
+ key: ModelIdSchema,
12
+ label: z.string().trim().min(1).max(120),
13
+ path: z.string().trim().min(1).max(500),
14
+ width: z.union([z.number().positive(), z.string().trim().min(1).max(100)]).optional(),
15
+ renderType: z.enum(['text', 'badge', 'datetime', 'count', 'json']).optional(),
16
+ badgeColor: z.string().trim().min(1).max(40).optional()
17
+ })
18
+
19
+ export const RecordColumnsConfigSchema = z
20
+ .object({
21
+ company: z.array(RecordColumnConfigSchema).optional(),
22
+ contact: z.array(RecordColumnConfigSchema).optional()
23
+ })
24
+ .refine((columns) => Boolean(columns.company?.length || columns.contact?.length), {
25
+ message: 'recordColumns must include at least one entity column set'
26
+ })
27
+
28
+ export const CredentialRequirementSchema = z.object({
29
+ key: ModelIdSchema,
30
+ provider: ModelIdSchema,
31
+ credentialType: z.enum(['api-key', 'api-key-secret', 'oauth', 'webhook-secret']),
32
+ label: z.string().trim().min(1).max(120),
33
+ required: z.boolean(),
34
+ selectionMode: z.enum(['single', 'multiple']).optional(),
35
+ inputPath: z.string().trim().min(1).max(500),
36
+ verifyOnRun: z.boolean().optional()
8
37
  })
9
38
 
10
39
  export const ProspectingBuildTemplateStepSchema = DisplayMetadataSchema.extend({
@@ -12,11 +41,16 @@ export const ProspectingBuildTemplateStepSchema = DisplayMetadataSchema.extend({
12
41
  primaryEntity: z.enum(['company', 'contact']),
13
42
  outputs: z.array(z.enum(['company', 'contact', 'export'])).min(1),
14
43
  stageKey: ModelIdSchema,
44
+ recordEntity: z.enum(['company', 'contact']).optional(),
45
+ recordsStageKey: ModelIdSchema.optional(),
46
+ recordSourceStageKey: ModelIdSchema.optional(),
15
47
  dependsOn: z.array(ModelIdSchema).optional(),
16
48
  dependencyMode: z.literal('per-record-eligibility'),
17
49
  capabilityKey: ModelIdSchema,
18
50
  defaultBatchSize: z.number().int().positive(),
19
- maxBatchSize: z.number().int().positive()
51
+ maxBatchSize: z.number().int().positive(),
52
+ recordColumns: RecordColumnsConfigSchema.optional(),
53
+ credentialRequirements: z.array(CredentialRequirementSchema).optional()
20
54
  }).refine((step) => step.defaultBatchSize <= step.maxBatchSize, {
21
55
  message: 'defaultBatchSize must be less than or equal to maxBatchSize',
22
56
  path: ['defaultBatchSize']
@@ -28,9 +62,68 @@ export const ProspectingBuildTemplateSchema = DisplayMetadataSchema.extend({
28
62
  })
29
63
 
30
64
  export type ListBuilderStep = z.infer<typeof ProspectingBuildTemplateStepSchema>
65
+ export type RecordColumnConfig = z.infer<typeof RecordColumnConfigSchema>
66
+ export type CredentialRequirement = z.infer<typeof CredentialRequirementSchema>
31
67
  export type TemplateName = 'localServices' | 'dtcApolloClickup'
32
68
  export type StepName = string
33
69
 
70
+ const DTC_RECORD_COLUMNS = {
71
+ populated: {
72
+ company: [
73
+ { key: 'name', label: 'Company', path: 'company.name' },
74
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
75
+ { key: 'employee-count', label: 'Employees', path: 'company.numEmployees', renderType: 'count' },
76
+ { key: 'apollo-industry', label: 'Apollo industry', path: 'company.category' },
77
+ { key: 'location', label: 'Location', path: 'company.locationState' }
78
+ ]
79
+ },
80
+ crawled: {
81
+ company: [
82
+ { key: 'name', label: 'Company', path: 'company.name' },
83
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
84
+ { key: 'page-count', label: 'Pages', path: 'company.enrichmentData.websiteCrawl.pageCount', renderType: 'count' },
85
+ { key: 'crawl-status', label: 'Crawl status', path: 'processingState.crawled.status', renderType: 'badge' }
86
+ ]
87
+ },
88
+ extracted: {
89
+ company: [
90
+ { key: 'name', label: 'Company', path: 'company.name' },
91
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
92
+ { key: 'description', label: 'Description', path: 'company.enrichmentData.websiteCrawl.companyDescription' },
93
+ { key: 'services', label: 'Services', path: 'company.enrichmentData.websiteCrawl.services', renderType: 'json' },
94
+ { key: 'automation-gaps', label: 'Automation gaps', path: 'company.enrichmentData.websiteCrawl.automationGaps', renderType: 'json' },
95
+ { key: 'contact-count', label: 'Contacts', path: 'company.enrichmentData.websiteCrawl.emailCount', renderType: 'count' }
96
+ ]
97
+ },
98
+ qualified: {
99
+ company: [
100
+ { key: 'name', label: 'Company', path: 'company.name' },
101
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
102
+ { key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
103
+ { key: 'signals', label: 'Signals', path: 'company.qualificationSignals', renderType: 'json' },
104
+ { key: 'disqualified-reason', label: 'Disqualified reason', path: 'processingState.qualified.data.disqualifiedReason' }
105
+ ]
106
+ },
107
+ decisionMakers: {
108
+ contact: [
109
+ { key: 'name', label: 'Name', path: 'contact.name' },
110
+ { key: 'title', label: 'Title', path: 'contact.title' },
111
+ { key: 'email', label: 'Email', path: 'contact.email' },
112
+ { key: 'linkedin', label: 'LinkedIn', path: 'contact.linkedinUrl' },
113
+ { key: 'priority-score', label: 'Priority', path: 'contact.enrichmentData.apollo.priorityScore', renderType: 'badge' }
114
+ ]
115
+ },
116
+ uploaded: {
117
+ company: [
118
+ { key: 'name', label: 'Company', path: 'company.name' },
119
+ { key: 'domain', label: 'Domain', path: 'company.domain' },
120
+ { key: 'contacts', label: 'Contacts', path: 'company.enrichmentData.approvedLeadListExport.contacts', renderType: 'json' },
121
+ { key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
122
+ { key: 'approval', label: 'Approval', path: 'company.enrichmentData.approvedLeadListExport.approvalStatus', renderType: 'badge' }
123
+ ]
124
+ }
125
+ } as const satisfies Record<string, z.infer<typeof RecordColumnsConfigSchema>>
126
+
34
127
  export const CapabilitySchema = z.object({
35
128
  id: ModelIdSchema,
36
129
  label: z.string(),
@@ -66,6 +159,13 @@ export const CAPABILITY_REGISTRY: CapabilityRegistry = [
66
159
  description: 'Check email deliverability before outreach.',
67
160
  resourceId: 'lgn-05-email-verification-workflow'
68
161
  },
162
+ {
163
+ id: 'lead-gen.company.apify-crawl',
164
+ label: 'Crawl websites',
165
+ description:
166
+ 'Crawl company websites via Apify and store raw page markdown in enrichmentData.websiteCrawl.pages for downstream LLM analysis.',
167
+ resourceId: 'lgn-02a-apify-website-crawl-workflow'
168
+ },
69
169
  {
70
170
  id: 'lead-gen.company.website-extract',
71
171
  label: 'Extract website signals',
@@ -217,7 +317,46 @@ export const PROSPECTING_STEPS = {
217
317
  dependencyMode: 'per-record-eligibility',
218
318
  capabilityKey: 'lead-gen.company.apollo-import',
219
319
  defaultBatchSize: 250,
220
- maxBatchSize: 1000
320
+ maxBatchSize: 1000,
321
+ recordColumns: DTC_RECORD_COLUMNS.populated,
322
+ credentialRequirements: [
323
+ {
324
+ key: 'apollo',
325
+ provider: 'apollo',
326
+ credentialType: 'api-key-secret',
327
+ label: 'Apollo API key',
328
+ required: true,
329
+ selectionMode: 'single',
330
+ inputPath: 'credential'
331
+ }
332
+ ]
333
+ },
334
+ apifyCrawl: {
335
+ id: 'apify-crawl',
336
+ label: 'Websites crawled',
337
+ description:
338
+ 'Crawl company websites via Apify and store raw page markdown in enrichmentData.websiteCrawl.pages for downstream LLM analysis. Overwrites the synthetic seed Apollo Import wrote with real page content.',
339
+ primaryEntity: 'company',
340
+ outputs: ['company'],
341
+ stageKey: 'crawled',
342
+ dependsOn: ['import-apollo-search'],
343
+ dependencyMode: 'per-record-eligibility',
344
+ capabilityKey: 'lead-gen.company.apify-crawl',
345
+ defaultBatchSize: 50,
346
+ maxBatchSize: 100,
347
+ recordColumns: DTC_RECORD_COLUMNS.crawled,
348
+ credentialRequirements: [
349
+ {
350
+ key: 'apify',
351
+ provider: 'apify',
352
+ credentialType: 'api-key-secret',
353
+ label: 'Apify API token',
354
+ required: true,
355
+ selectionMode: 'single',
356
+ inputPath: 'credential',
357
+ verifyOnRun: true
358
+ }
359
+ ]
221
360
  },
222
361
  analyzeWebsites: {
223
362
  id: 'analyze-websites',
@@ -226,11 +365,12 @@ export const PROSPECTING_STEPS = {
226
365
  primaryEntity: 'company',
227
366
  outputs: ['company'],
228
367
  stageKey: 'extracted',
229
- dependsOn: ['import-apollo-search'],
368
+ dependsOn: ['apify-crawl'],
230
369
  dependencyMode: 'per-record-eligibility',
231
370
  capabilityKey: 'lead-gen.company.website-extract',
232
371
  defaultBatchSize: 50,
233
- maxBatchSize: 100
372
+ maxBatchSize: 100,
373
+ recordColumns: DTC_RECORD_COLUMNS.extracted
234
374
  },
235
375
  scoreDtcFit: {
236
376
  id: 'score-dtc-fit',
@@ -243,7 +383,8 @@ export const PROSPECTING_STEPS = {
243
383
  dependencyMode: 'per-record-eligibility',
244
384
  capabilityKey: 'lead-gen.company.dtc-subscription-qualify',
245
385
  defaultBatchSize: 100,
246
- maxBatchSize: 250
386
+ maxBatchSize: 250,
387
+ recordColumns: DTC_RECORD_COLUMNS.qualified
247
388
  },
248
389
  enrichDecisionMakers: {
249
390
  id: 'enrich-decision-makers',
@@ -253,37 +394,53 @@ export const PROSPECTING_STEPS = {
253
394
  primaryEntity: 'company',
254
395
  outputs: ['contact'],
255
396
  stageKey: 'decision-makers-enriched',
397
+ recordEntity: 'contact',
256
398
  dependsOn: ['score-dtc-fit'],
257
399
  dependencyMode: 'per-record-eligibility',
258
400
  capabilityKey: 'lead-gen.contact.apollo-decision-maker-enrich',
259
401
  defaultBatchSize: 100,
260
- maxBatchSize: 250
261
- },
262
- verifyEmails: {
263
- id: 'verify-emails',
264
- label: 'Emails verified',
265
- description: 'Verify deliverability before the QC and handoff step.',
266
- primaryEntity: 'contact',
267
- outputs: ['contact'],
268
- stageKey: 'verified',
269
- dependsOn: ['enrich-decision-makers'],
270
- dependencyMode: 'per-record-eligibility',
271
- capabilityKey: 'lead-gen.contact.verify-email',
272
- defaultBatchSize: 250,
273
- maxBatchSize: 500
402
+ maxBatchSize: 250,
403
+ recordColumns: DTC_RECORD_COLUMNS.decisionMakers,
404
+ credentialRequirements: [
405
+ {
406
+ key: 'apollo',
407
+ provider: 'apollo',
408
+ credentialType: 'api-key-secret',
409
+ label: 'Apollo API key',
410
+ required: true,
411
+ selectionMode: 'single',
412
+ inputPath: 'credential'
413
+ }
414
+ ]
274
415
  },
275
416
  reviewAndExport: {
276
417
  id: 'review-and-export',
277
418
  label: 'Reviewed and exported',
278
- description: 'Operator QC approves or rejects leads, then approved records are exported as a lead list.',
419
+ description:
420
+ 'Operator QC approves or rejects qualified companies, then approved records are exported as a lead list with unverified emails.',
279
421
  primaryEntity: 'company',
280
422
  outputs: ['export'],
281
423
  stageKey: 'uploaded',
282
- dependsOn: ['verify-emails'],
424
+ recordsStageKey: 'uploaded',
425
+ recordSourceStageKey: 'qualified',
426
+ dependsOn: ['enrich-decision-makers'],
283
427
  dependencyMode: 'per-record-eligibility',
284
428
  capabilityKey: 'lead-gen.export.list',
285
429
  defaultBatchSize: 100,
286
- maxBatchSize: 250
430
+ maxBatchSize: 250,
431
+ recordColumns: DTC_RECORD_COLUMNS.uploaded,
432
+ credentialRequirements: [
433
+ {
434
+ key: 'clickup',
435
+ provider: 'clickup',
436
+ credentialType: 'api-key-secret',
437
+ label: 'ClickUp API token',
438
+ required: true,
439
+ selectionMode: 'single',
440
+ inputPath: 'clickupCredential',
441
+ verifyOnRun: true
442
+ }
443
+ ]
287
444
  }
288
445
  }
289
446
  } as const satisfies Record<TemplateName, Record<StepName, ListBuilderStep>>
@@ -311,7 +468,7 @@ function leadGenStagesForEntity(
311
468
  entity: LeadGenStageCatalogEntry['entity']
312
469
  ): z.infer<typeof ProspectingLifecycleStageSchema>[] {
313
470
  return Object.values(LEAD_GEN_STAGE_CATALOG)
314
- .filter((stage) => stage.entity === entity)
471
+ .filter((stage) => stage.entity === entity || stage.additionalEntities?.includes(entity))
315
472
  .sort((a, b) => a.order - b.order)
316
473
  .map(toProspectingLifecycleStage)
317
474
  }
@@ -346,10 +503,10 @@ export const DEFAULT_ORGANIZATION_MODEL_PROSPECTING: z.infer<typeof Organization
346
503
  'Prospecting pipeline for DTC subscription or subscription-ready brands where Apollo is the source and contact-enrichment layer, Elevasis handles company research and fit scoring, and approved leads export as an approved lead list.',
347
504
  steps: [
348
505
  PROSPECTING_STEPS.dtcApolloClickup.importApolloSearch,
506
+ PROSPECTING_STEPS.dtcApolloClickup.apifyCrawl,
349
507
  PROSPECTING_STEPS.dtcApolloClickup.analyzeWebsites,
350
508
  PROSPECTING_STEPS.dtcApolloClickup.scoreDtcFit,
351
509
  PROSPECTING_STEPS.dtcApolloClickup.enrichDecisionMakers,
352
- PROSPECTING_STEPS.dtcApolloClickup.verifyEmails,
353
510
  PROSPECTING_STEPS.dtcApolloClickup.reviewAndExport
354
511
  ]
355
512
  }
@@ -0,0 +1,88 @@
1
+ import { z } from 'zod'
2
+ import { ModelIdSchema } from './shared'
3
+ import { SystemIdSchema } from './systems'
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Resources domain
7
+ // ---------------------------------------------------------------------------
8
+ //
9
+ // Resources are governance-only OM descriptors. Runtime code imports these
10
+ // descriptors and attaches executable behavior; it does not re-author identity.
11
+
12
+ export const ResourceKindSchema = z.enum(['workflow', 'agent', 'integration'])
13
+ export const ResourceGovernanceStatusSchema = z.enum(['active', 'deprecated', 'archived'])
14
+ export const AgentKindSchema = z.enum(['orchestrator', 'specialist', 'utility', 'system'])
15
+
16
+ export const ResourceIdSchema = z
17
+ .string()
18
+ .trim()
19
+ .min(1)
20
+ .max(255)
21
+ .regex(/^[A-Za-z0-9]+(?:[-._][A-Za-z0-9]+)*$/, 'Resource IDs must use letters, numbers, -, _, or . separators')
22
+
23
+ const ResourceEntryBaseSchema = z.object({
24
+ /** Canonical resource id; runtime resourceId derives from this value. */
25
+ id: ResourceIdSchema,
26
+ /** Required single System membership. */
27
+ systemId: SystemIdSchema,
28
+ /** Optional role responsible for maintaining this resource. */
29
+ ownerRoleId: ModelIdSchema.optional(),
30
+ status: ResourceGovernanceStatusSchema
31
+ })
32
+
33
+ export const WorkflowResourceEntrySchema = ResourceEntryBaseSchema.extend({
34
+ kind: z.literal('workflow'),
35
+ /** Mirrors WorkflowConfig.capabilityKey when the runtime workflow has one. */
36
+ capabilityKey: z.string().trim().min(1).max(255).optional()
37
+ })
38
+
39
+ export const AgentResourceEntrySchema = ResourceEntryBaseSchema.extend({
40
+ kind: z.literal('agent'),
41
+ /** Mirrors code-side AgentConfig.kind. */
42
+ agentKind: AgentKindSchema,
43
+ /** Role this agent embodies, if any. */
44
+ actsAsRoleId: ModelIdSchema.optional(),
45
+ /** Mirrors AgentConfig.sessionCapable. */
46
+ sessionCapable: z.boolean()
47
+ })
48
+
49
+ export const IntegrationResourceEntrySchema = ResourceEntryBaseSchema.extend({
50
+ kind: z.literal('integration'),
51
+ provider: z.string().trim().min(1).max(100)
52
+ })
53
+
54
+ export const ResourceEntrySchema = z.discriminatedUnion('kind', [
55
+ WorkflowResourceEntrySchema,
56
+ AgentResourceEntrySchema,
57
+ IntegrationResourceEntrySchema
58
+ ])
59
+
60
+ export const ResourcesDomainSchema = z.object({
61
+ entries: z.array(ResourceEntrySchema).default([])
62
+ })
63
+
64
+ export const DEFAULT_ORGANIZATION_MODEL_RESOURCES: z.infer<typeof ResourcesDomainSchema> = {
65
+ entries: []
66
+ }
67
+
68
+ export function defineResource<const TResource extends ResourceEntry>(resource: TResource): TResource {
69
+ return ResourceEntrySchema.parse(resource) as TResource
70
+ }
71
+
72
+ export function defineResources<const TResources extends Record<string, ResourceEntry>>(resources: TResources): TResources {
73
+ for (const resource of Object.values(resources)) {
74
+ ResourceEntrySchema.parse(resource)
75
+ }
76
+
77
+ return resources
78
+ }
79
+
80
+ export type ResourceId = z.infer<typeof ResourceIdSchema>
81
+ export type ResourceKind = z.infer<typeof ResourceKindSchema>
82
+ export type ResourceGovernanceStatus = z.infer<typeof ResourceGovernanceStatusSchema>
83
+ export type ResourceAgentKind = z.infer<typeof AgentKindSchema>
84
+ export type WorkflowResourceEntry = z.infer<typeof WorkflowResourceEntrySchema>
85
+ export type AgentResourceEntry = z.infer<typeof AgentResourceEntrySchema>
86
+ export type IntegrationResourceEntry = z.infer<typeof IntegrationResourceEntrySchema>
87
+ export type ResourceEntry = z.infer<typeof ResourceEntrySchema>
88
+ export type ResourcesDomain = z.infer<typeof ResourcesDomainSchema>
@@ -1,55 +1,93 @@
1
- import { z } from 'zod'
2
-
3
- // ---------------------------------------------------------------------------
4
- // Role schema one entry per distinct role in the organization's chart.
5
- // Inspired by the EOS Accountability Chart but uses plain-language field names
6
- // throughout. No EOS jargon: "title" (not seatTitle), "responsibilities"
7
- // (not accountabilities), "reportsToId", "heldBy".
8
- //
9
- // Cross-reference: `reportsToId` (when present) must resolve to another
10
- // `roles[].id` in the same collection. Enforcement is via
11
- // `OrganizationModelSchema.superRefine()` — not at the individual schema level.
12
- // Cycle detection is NOT enforced (known limitation; document if needed).
13
- // ---------------------------------------------------------------------------
14
-
15
- export const RoleSchema = z.object({
16
- /** Stable unique identifier for the role (e.g. "role-ceo", "role-head-of-sales"). */
17
- id: z.string().trim().min(1).max(100),
18
- /** Human-readable title shown to agents and in UI (e.g. "CEO", "Head of Sales"). */
19
- title: z.string().trim().min(1).max(200),
20
- /**
21
- * List of responsibilities this role owns — plain-language descriptions of
22
- * what the person in this role is accountable for delivering.
23
- * Defaults to empty array so minimal role definitions stay concise.
24
- */
25
- responsibilities: z.array(z.string().trim().max(500)).default([]),
26
- /**
27
- * Optional: ID of another role this role reports to.
28
- * When present, must reference another `roles[].id` in the same organization.
29
- * Cross-reference enforced in `OrganizationModelSchema.superRefine()`.
30
- * Absence indicates a top-level role (no reporting line).
31
- */
32
- reportsToId: z.string().trim().min(1).max(100).optional(),
33
- /**
34
- * Optional: name or email of the person currently holding this role.
35
- * Free-form string supports "Alice Johnson", "alice@example.com", or
36
- * any human-readable identifier. Not validated against any user registry.
37
- */
38
- heldBy: z.string().trim().max(200).optional()
39
- })
40
-
41
- // ---------------------------------------------------------------------------
42
- // Roles domain schema — a collection of roles.
43
- // ---------------------------------------------------------------------------
44
-
45
- export const RolesDomainSchema = z.object({
46
- roles: z.array(RoleSchema).default([])
47
- })
48
-
49
- // ---------------------------------------------------------------------------
50
- // Seed empty by default; adapters populate with real role definitions.
51
- // ---------------------------------------------------------------------------
52
-
53
- export const DEFAULT_ORGANIZATION_MODEL_ROLES: z.infer<typeof RolesDomainSchema> = {
54
- roles: []
55
- }
1
+ import { z } from 'zod'
2
+ import { ResourceIdSchema } from './resources'
3
+ import { ModelIdSchema } from './shared'
4
+ import { SystemIdSchema } from './systems'
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Role schema - one entry per distinct role in the organization's chart.
8
+ // Inspired by the EOS Accountability Chart but uses plain-language field names
9
+ // throughout. No EOS jargon: "title" (not seatTitle), "responsibilities"
10
+ // (not accountabilities), "reportsToId", "heldBy".
11
+ //
12
+ // Cross-reference enforcement lives in `OrganizationModelSchema.superRefine()`:
13
+ // `reportsToId` must resolve to another Role, `responsibleFor` entries must
14
+ // resolve to Systems, and agent holders must resolve to Agent Resources.
15
+ // Cycle detection is NOT enforced (known limitation; document if needed).
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export const RoleIdSchema = ModelIdSchema
19
+
20
+ export const HumanRoleHolderSchema = z.object({
21
+ kind: z.literal('human'),
22
+ userId: z.string().trim().min(1).max(200)
23
+ })
24
+
25
+ export const AgentRoleHolderSchema = z.object({
26
+ kind: z.literal('agent'),
27
+ agentId: ResourceIdSchema
28
+ })
29
+
30
+ export const TeamRoleHolderSchema = z.object({
31
+ kind: z.literal('team'),
32
+ memberIds: z.array(z.string().trim().min(1).max(200)).min(1)
33
+ })
34
+
35
+ export const RoleHolderSchema = z.discriminatedUnion('kind', [
36
+ HumanRoleHolderSchema,
37
+ AgentRoleHolderSchema,
38
+ TeamRoleHolderSchema
39
+ ])
40
+
41
+ export const RoleHoldersSchema = z.union([RoleHolderSchema, z.array(RoleHolderSchema).min(1)])
42
+
43
+ export const RoleSchema = z.object({
44
+ /** Stable unique identifier for the role (e.g. "role-ceo", "role-head-of-sales"). */
45
+ id: RoleIdSchema,
46
+ /** Human-readable title shown to agents and in UI (e.g. "CEO", "Head of Sales"). */
47
+ title: z.string().trim().min(1).max(200),
48
+ /**
49
+ * List of responsibilities this role owns - plain-language descriptions of
50
+ * what the person in this role is accountable for delivering.
51
+ * Defaults to empty array so minimal role definitions stay concise.
52
+ */
53
+ responsibilities: z.array(z.string().trim().max(500)).default([]),
54
+ /**
55
+ * Optional: ID of another role this role reports to.
56
+ * When present, must reference another `roles[].id` in the same organization.
57
+ */
58
+ reportsToId: RoleIdSchema.optional(),
59
+ /**
60
+ * Optional: human, agent, or team holder currently filling this role.
61
+ * Agent holders reference OM Resource IDs and are validated at the model level.
62
+ */
63
+ heldBy: RoleHoldersSchema.optional(),
64
+ /**
65
+ * Optional Systems this role is accountable for.
66
+ * Cross-reference enforced in `OrganizationModelSchema.superRefine()`.
67
+ */
68
+ responsibleFor: z.array(SystemIdSchema).optional()
69
+ })
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Roles domain schema - a collection of roles.
73
+ // ---------------------------------------------------------------------------
74
+
75
+ export const RolesDomainSchema = z.object({
76
+ roles: z.array(RoleSchema).default([])
77
+ })
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Seed - empty by default; adapters populate with real role definitions.
81
+ // ---------------------------------------------------------------------------
82
+
83
+ export const DEFAULT_ORGANIZATION_MODEL_ROLES: z.infer<typeof RolesDomainSchema> = {
84
+ roles: []
85
+ }
86
+
87
+ export type RoleId = z.infer<typeof RoleIdSchema>
88
+ export type HumanRoleHolder = z.infer<typeof HumanRoleHolderSchema>
89
+ export type AgentRoleHolder = z.infer<typeof AgentRoleHolderSchema>
90
+ export type TeamRoleHolder = z.infer<typeof TeamRoleHolderSchema>
91
+ export type RoleHolder = z.infer<typeof RoleHolderSchema>
92
+ export type Role = z.infer<typeof RoleSchema>
93
+ export type RolesDomain = z.infer<typeof RolesDomainSchema>
@@ -113,6 +113,7 @@ export const DEFAULT_ORGANIZATION_MODEL_SALES: z.infer<typeof OrganizationModelS
113
113
  // - interested (instantly-reply-handler.ts → contacts, initial reply transition)
114
114
  // prospecting/:
115
115
  // - populated (apify-acquire.ts, apify-scrape.ts → companies)
116
+ // - crawled (apify-website-crawl.ts → companies)
116
117
  // - extracted (website-extract.ts → companies)
117
118
  // - discovered (email-discovery.ts, anymailfinder-enrich.ts → contacts)
118
119
  // - verified (email-verification.ts → contacts)
@@ -414,7 +415,7 @@ export const ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE: StatefulPipelineDefinition =
414
415
  {
415
416
  stageKey: 'outreach',
416
417
  label: 'Outreach',
417
- states: [PENDING_STATE]
418
+ states: [PENDING_STATE, { stateKey: 'uploaded', label: 'Uploaded' }]
418
419
  },
419
420
  {
420
421
  stageKey: 'prospecting',
@@ -472,6 +473,15 @@ export interface LeadGenStageCatalogEntry {
472
473
  order: number
473
474
  /** Which entity's processing_state jsonb carries this stage status. */
474
475
  entity: 'company' | 'contact'
476
+ /** Additional entities allowed to write/read this processing_state key. */
477
+ additionalEntities?: Array<'company' | 'contact'>
478
+ /**
479
+ * Optional read-side override for Records views when a company-scoped step
480
+ * produces records on a different entity.
481
+ */
482
+ recordEntity?: 'company' | 'contact'
483
+ /** Stage key to read from recordEntity.processing_state for Records views. */
484
+ recordStageKey?: string
475
485
  }
476
486
 
477
487
  /**
@@ -496,6 +506,14 @@ export const LEAD_GEN_STAGE_CATALOG: Record<string, LeadGenStageCatalogEntry> =
496
506
  order: 2,
497
507
  entity: 'company'
498
508
  },
509
+ crawled: {
510
+ key: 'crawled',
511
+ label: 'Websites crawled',
512
+ description:
513
+ 'Company websites have been crawled (e.g. via Apify) and raw page content stored for downstream LLM analysis.',
514
+ order: 2.5,
515
+ entity: 'company'
516
+ },
499
517
  extracted: {
500
518
  key: 'extracted',
501
519
  label: 'Websites analyzed',
@@ -515,7 +533,9 @@ export const LEAD_GEN_STAGE_CATALOG: Record<string, LeadGenStageCatalogEntry> =
515
533
  label: 'Decision-makers found',
516
534
  description: 'Decision-maker contacts discovered and attached to a qualified company.',
517
535
  order: 6,
518
- entity: 'company'
536
+ entity: 'company',
537
+ recordEntity: 'contact',
538
+ recordStageKey: 'discovered'
519
539
  },
520
540
 
521
541
  // Prospecting — contact discovery
@@ -556,7 +576,8 @@ export const LEAD_GEN_STAGE_CATALOG: Record<string, LeadGenStageCatalogEntry> =
556
576
  label: 'Reviewed and exported',
557
577
  description: 'Approved records have been reviewed and exported for handoff.',
558
578
  order: 10,
559
- entity: 'contact'
579
+ entity: 'company',
580
+ additionalEntities: ['contact']
560
581
  },
561
582
  interested: {
562
583
  key: 'interested',
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod'
2
+ import { DescriptionSchema, LabelSchema, ModelIdSchema, ReferenceIdsSchema } from './shared'
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Systems domain
6
+ // ---------------------------------------------------------------------------
7
+ //
8
+ // A System is a tenant-defined bounded context that groups operational
9
+ // resources and carries governance metadata. The shared schema validates the
10
+ // shape and references; each tenant supplies its own catalog.
11
+
12
+ export const SystemKindSchema = z.enum(['product', 'operational', 'platform', 'diagnostic'])
13
+ export const SystemStatusSchema = z.enum(['active', 'deprecated', 'archived'])
14
+ export const SystemIdSchema = ModelIdSchema
15
+
16
+ export const SystemEntrySchema = z.object({
17
+ /** Stable tenant-defined system id (e.g. "sys.lead-gen"). */
18
+ id: SystemIdSchema,
19
+ /** Human-readable system title shown in governance and operations UI. */
20
+ title: LabelSchema,
21
+ /** One-paragraph purpose statement for the bounded context. */
22
+ description: DescriptionSchema,
23
+ /** Closed system shape enum; catalog values remain tenant-defined. */
24
+ kind: SystemKindSchema,
25
+ /** Optional role responsible for this system. */
26
+ responsibleRoleId: ModelIdSchema.optional(),
27
+ /** Optional knowledge nodes that govern this system. */
28
+ governedByKnowledge: ReferenceIdsSchema,
29
+ /** Optional goals this system contributes to. */
30
+ drivesGoals: ReferenceIdsSchema,
31
+ status: SystemStatusSchema
32
+ })
33
+
34
+ export const SystemsDomainSchema = z.object({
35
+ systems: z.array(SystemEntrySchema).default([])
36
+ })
37
+
38
+ export const DEFAULT_ORGANIZATION_MODEL_SYSTEMS: z.infer<typeof SystemsDomainSchema> = {
39
+ systems: []
40
+ }
41
+
42
+ export type SystemId = z.infer<typeof SystemIdSchema>
43
+ export type SystemKind = z.infer<typeof SystemKindSchema>
44
+ export type SystemStatus = z.infer<typeof SystemStatusSchema>
45
+ export type SystemEntry = z.infer<typeof SystemEntrySchema>
46
+ export type SystemsDomain = z.infer<typeof SystemsDomainSchema>
@@ -18,6 +18,7 @@ export const ORGANIZATION_MODEL_ICON_TOKENS = [
18
18
  'knowledge.reference',
19
19
  'feature.dashboard',
20
20
  'feature.calendar',
21
+ 'feature.business',
21
22
  'feature.sales',
22
23
  'feature.crm',
23
24
  'feature.finance',