@elevasis/core 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/index.d.ts +2518 -2169
  2. package/dist/index.js +2495 -1095
  3. package/dist/knowledge/index.d.ts +706 -1044
  4. package/dist/knowledge/index.js +9 -9
  5. package/dist/organization-model/index.d.ts +2518 -2169
  6. package/dist/organization-model/index.js +2495 -1095
  7. package/dist/test-utils/index.d.ts +826 -1014
  8. package/dist/test-utils/index.js +1894 -1032
  9. package/package.json +3 -3
  10. package/src/__tests__/template-core-compatibility.test.ts +11 -79
  11. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +852 -397
  12. package/src/auth/multi-tenancy/permissions.ts +20 -8
  13. package/src/business/README.md +2 -2
  14. package/src/business/acquisition/api-schemas.test.ts +175 -2
  15. package/src/business/acquisition/api-schemas.ts +132 -16
  16. package/src/business/acquisition/build-templates.test.ts +4 -4
  17. package/src/business/acquisition/build-templates.ts +72 -30
  18. package/src/business/acquisition/crm-state-actions.test.ts +13 -11
  19. package/src/business/acquisition/index.ts +12 -0
  20. package/src/business/acquisition/types.ts +7 -3
  21. package/src/business/clients/api-schemas.test.ts +115 -0
  22. package/src/business/clients/api-schemas.ts +158 -0
  23. package/src/business/clients/index.ts +1 -0
  24. package/src/business/deals/api-schemas.ts +8 -0
  25. package/src/business/index.ts +5 -2
  26. package/src/business/projects/types.ts +19 -0
  27. package/src/execution/engine/__tests__/fixtures/test-agents.ts +10 -8
  28. package/src/execution/engine/agent/core/__tests__/agent.test.ts +16 -12
  29. package/src/execution/engine/agent/core/__tests__/error-passthrough.test.ts +4 -3
  30. package/src/execution/engine/agent/core/types.ts +25 -15
  31. package/src/execution/engine/agent/index.ts +6 -4
  32. package/src/execution/engine/agent/reasoning/__tests__/request-builder.test.ts +24 -18
  33. package/src/execution/engine/index.ts +3 -0
  34. package/src/execution/engine/workflow/types.ts +9 -2
  35. package/src/knowledge/README.md +8 -7
  36. package/src/knowledge/__tests__/queries.test.ts +74 -73
  37. package/src/knowledge/format.ts +10 -9
  38. package/src/knowledge/index.ts +1 -1
  39. package/src/knowledge/published.ts +1 -1
  40. package/src/knowledge/queries.ts +26 -25
  41. package/src/organization-model/README.md +73 -26
  42. package/src/organization-model/__tests__/content-kinds-registry.test.ts +210 -0
  43. package/src/organization-model/__tests__/defaults.test.ts +76 -96
  44. package/src/organization-model/__tests__/domains/actions.test.ts +56 -0
  45. package/src/organization-model/__tests__/domains/customers.test.ts +299 -295
  46. package/src/organization-model/__tests__/domains/entities.test.ts +56 -0
  47. package/src/organization-model/__tests__/domains/goals.test.ts +493 -479
  48. package/src/organization-model/__tests__/domains/identity.test.ts +280 -279
  49. package/src/organization-model/__tests__/domains/navigation.test.ts +268 -212
  50. package/src/organization-model/__tests__/domains/offerings.test.ts +414 -419
  51. package/src/organization-model/__tests__/domains/policies.test.ts +323 -0
  52. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +271 -271
  53. package/src/organization-model/__tests__/domains/resources.test.ts +310 -0
  54. package/src/organization-model/__tests__/domains/roles.test.ts +463 -347
  55. package/src/organization-model/__tests__/domains/statuses.test.ts +246 -243
  56. package/src/organization-model/__tests__/domains/systems.test.ts +209 -0
  57. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +361 -0
  58. package/src/organization-model/__tests__/foundation.test.ts +74 -102
  59. package/src/organization-model/__tests__/get-resources-for-system.test.ts +144 -0
  60. package/src/organization-model/__tests__/graph.test.ts +899 -71
  61. package/src/organization-model/__tests__/knowledge.test.ts +209 -49
  62. package/src/organization-model/__tests__/lookup-helpers.test.ts +438 -0
  63. package/src/organization-model/__tests__/migration-helpers.test.ts +591 -0
  64. package/src/organization-model/__tests__/prospecting-ssot.test.ts +36 -27
  65. package/src/organization-model/__tests__/recursive-system-schema.test.ts +520 -0
  66. package/src/organization-model/__tests__/resolve.test.ts +174 -23
  67. package/src/organization-model/__tests__/schema.test.ts +291 -114
  68. package/src/organization-model/__tests__/surface-projection.test.ts +207 -97
  69. package/src/organization-model/catalogs/lead-gen.ts +144 -0
  70. package/src/organization-model/content-kinds/config.ts +36 -0
  71. package/src/organization-model/content-kinds/index.ts +74 -0
  72. package/src/organization-model/content-kinds/pipeline.ts +68 -0
  73. package/src/organization-model/content-kinds/registry.ts +44 -0
  74. package/src/organization-model/content-kinds/status.ts +71 -0
  75. package/src/organization-model/content-kinds/template.ts +83 -0
  76. package/src/organization-model/content-kinds/types.ts +117 -0
  77. package/src/organization-model/contracts.ts +13 -3
  78. package/src/organization-model/defaults.ts +499 -86
  79. package/src/organization-model/domains/actions.ts +239 -0
  80. package/src/organization-model/domains/customers.ts +78 -75
  81. package/src/organization-model/domains/entities.ts +144 -0
  82. package/src/organization-model/domains/goals.ts +83 -80
  83. package/src/organization-model/domains/knowledge.ts +76 -17
  84. package/src/organization-model/domains/navigation.ts +107 -384
  85. package/src/organization-model/domains/offerings.ts +71 -66
  86. package/src/organization-model/domains/policies.ts +102 -0
  87. package/src/organization-model/domains/projects.ts +14 -48
  88. package/src/organization-model/domains/prospecting.ts +62 -181
  89. package/src/organization-model/domains/resources.ts +145 -0
  90. package/src/organization-model/domains/roles.ts +96 -55
  91. package/src/organization-model/domains/sales.ts +10 -219
  92. package/src/organization-model/domains/shared.ts +57 -57
  93. package/src/organization-model/domains/statuses.ts +339 -130
  94. package/src/organization-model/domains/systems.ts +203 -0
  95. package/src/organization-model/foundation.ts +54 -67
  96. package/src/organization-model/graph/build.ts +682 -54
  97. package/src/organization-model/graph/link.ts +1 -1
  98. package/src/organization-model/graph/schema.ts +24 -9
  99. package/src/organization-model/graph/types.ts +20 -7
  100. package/src/organization-model/helpers.ts +231 -26
  101. package/src/organization-model/icons.ts +1 -0
  102. package/src/organization-model/index.ts +118 -5
  103. package/src/organization-model/migration-helpers.ts +249 -0
  104. package/src/organization-model/organization-graph.mdx +16 -15
  105. package/src/organization-model/organization-model.mdx +111 -44
  106. package/src/organization-model/published.ts +172 -19
  107. package/src/organization-model/resolve.ts +117 -54
  108. package/src/organization-model/schema.ts +654 -112
  109. package/src/organization-model/surface-projection.ts +116 -122
  110. package/src/organization-model/types.ts +146 -20
  111. package/src/platform/api/types.ts +38 -35
  112. package/src/platform/constants/versions.ts +1 -1
  113. package/src/platform/registry/__tests__/command-view.test.ts +6 -8
  114. package/src/platform/registry/__tests__/resource-link.test.ts +13 -8
  115. package/src/platform/registry/__tests__/resource-registry.integration.test.ts +16 -31
  116. package/src/platform/registry/__tests__/resource-registry.nested-systems.test.ts +245 -0
  117. package/src/platform/registry/__tests__/resource-registry.test.ts +2053 -2005
  118. package/src/platform/registry/__tests__/validation.test.ts +1347 -1086
  119. package/src/platform/registry/index.ts +14 -0
  120. package/src/platform/registry/resource-registry.ts +52 -2
  121. package/src/platform/registry/serialization.ts +241 -202
  122. package/src/platform/registry/serialized-types.ts +1 -0
  123. package/src/platform/registry/types.ts +411 -361
  124. package/src/platform/registry/validation.ts +745 -513
  125. package/src/projects/api-schemas.ts +290 -267
  126. package/src/reference/_generated/contracts.md +853 -397
  127. package/src/reference/glossary.md +23 -18
  128. package/src/supabase/database.types.ts +181 -0
  129. package/src/test-utils/test-utils.test.ts +1 -6
  130. package/src/organization-model/__tests__/domains/operations.test.ts +0 -203
  131. package/src/organization-model/domains/features.ts +0 -31
  132. package/src/organization-model/domains/operations.ts +0 -85
@@ -1,28 +1,60 @@
1
- import { DEFAULT_ORGANIZATION_MODEL_PROSPECTING } from '../../organization-model/domains/prospecting'
2
- import type { BuildPlanSnapshot, BuildPlanSnapshotStep } from './types'
3
-
4
- export const PROSPECTING_BUILD_TEMPLATE_OPTIONS = DEFAULT_ORGANIZATION_MODEL_PROSPECTING.buildTemplates.map(
5
- ({ id, label, description }) => ({
6
- id,
7
- label,
8
- description
9
- })
10
- )
11
-
12
- export const DEFAULT_PROSPECTING_BUILD_TEMPLATE_ID = DEFAULT_ORGANIZATION_MODEL_PROSPECTING.defaultBuildTemplateId
13
-
14
- export function isProspectingBuildTemplateId(value: string): boolean {
15
- return PROSPECTING_BUILD_TEMPLATE_OPTIONS.some((template) => template.id === value)
16
- }
17
-
18
- export function createBuildPlanSnapshotFromTemplateId(templateId: string): BuildPlanSnapshot | null {
19
- const template = DEFAULT_ORGANIZATION_MODEL_PROSPECTING.buildTemplates.find((item) => item.id === templateId)
20
- if (!template) return null
21
-
22
- return {
23
- templateId: template.id,
24
- templateLabel: template.label,
25
- steps: template.steps.map((step): BuildPlanSnapshotStep => {
1
+ // Phase 4: DEFAULT_ORGANIZATION_MODEL_PROSPECTING was deleted (compound domain removed per D8).
2
+ // Build-template definitions are now sourced inline from PROSPECTING_STEPS, which retains
3
+ // the step catalog. The template catalog (id, label, description, steps[]) is declared here
4
+ // as a local constant — Wave 2 will author these as system.content entries on the canonical OM.
5
+ import {
6
+ PROSPECTING_STEPS,
7
+ type CredentialRequirement,
8
+ type ListBuilderStep,
9
+ type RecordColumnConfig
10
+ } from '../../organization-model/domains/prospecting'
11
+ import type { BuildPlanSnapshot, BuildPlanSnapshotStep } from './types'
12
+
13
+ interface BuildTemplateCatalogEntry {
14
+ id: string
15
+ label: string
16
+ description?: string
17
+ steps: ListBuilderStep[]
18
+ }
19
+
20
+ // Template catalog — mirrors the data that lived in DEFAULT_ORGANIZATION_MODEL_PROSPECTING.buildTemplates.
21
+ // Wave 2 canonical OM will author these as (kind:'schema', type:'template') content nodes.
22
+ const BUILD_TEMPLATE_CATALOG: BuildTemplateCatalogEntry[] = [
23
+ {
24
+ id: 'local-services',
25
+ label: 'Local Services',
26
+ description: 'Source, analyze, qualify, and personalize local service businesses for outreach.',
27
+ steps: Object.values(PROSPECTING_STEPS.localServices)
28
+ },
29
+ {
30
+ id: 'dtc-subscription-apollo-clickup',
31
+ label: 'DTC Subscription (Apollo + ClickUp)',
32
+ description:
33
+ 'Import DTC brand leads from Apollo, crawl their websites, score fit, enrich contacts, and export via ClickUp.',
34
+ steps: Object.values(PROSPECTING_STEPS.dtcApolloClickup)
35
+ }
36
+ ]
37
+
38
+ export const DEFAULT_PROSPECTING_BUILD_TEMPLATE_ID = 'local-services'
39
+
40
+ export const PROSPECTING_BUILD_TEMPLATE_OPTIONS = BUILD_TEMPLATE_CATALOG.map(({ id, label, description }) => ({
41
+ id,
42
+ label,
43
+ description
44
+ }))
45
+
46
+ export function isProspectingBuildTemplateId(value: string): boolean {
47
+ return PROSPECTING_BUILD_TEMPLATE_OPTIONS.some((template) => template.id === value)
48
+ }
49
+
50
+ export function createBuildPlanSnapshotFromTemplateId(templateId: string): BuildPlanSnapshot | null {
51
+ const template = BUILD_TEMPLATE_CATALOG.find((item: BuildTemplateCatalogEntry) => item.id === templateId)
52
+ if (!template) return null
53
+
54
+ return {
55
+ templateId: template.id,
56
+ templateLabel: template.label,
57
+ steps: template.steps.map((step: ListBuilderStep): BuildPlanSnapshotStep => {
26
58
  const snapshotStep: BuildPlanSnapshotStep = {
27
59
  id: step.id,
28
60
  label: step.label,
@@ -32,7 +64,7 @@ export function createBuildPlanSnapshotFromTemplateId(templateId: string): Build
32
64
  recordsStageKey: step.recordsStageKey ?? step.stageKey,
33
65
  recordSourceStageKey: step.recordSourceStageKey ?? step.recordsStageKey ?? step.stageKey,
34
66
  dependencyMode: step.dependencyMode,
35
- capabilityKey: step.capabilityKey,
67
+ actionKey: step.actionKey,
36
68
  defaultBatchSize: step.defaultBatchSize,
37
69
  maxBatchSize: step.maxBatchSize
38
70
  }
@@ -41,16 +73,26 @@ export function createBuildPlanSnapshotFromTemplateId(templateId: string): Build
41
73
  if (step.recordEntity) snapshotStep.recordEntity = step.recordEntity
42
74
  if (step.dependsOn?.length) snapshotStep.dependsOn = [...step.dependsOn]
43
75
  if (step.credentialRequirements?.length) {
44
- snapshotStep.credentialRequirements = step.credentialRequirements.map((requirement) => ({ ...requirement }))
76
+ snapshotStep.credentialRequirements = step.credentialRequirements.map((requirement: CredentialRequirement) => ({
77
+ ...requirement
78
+ }))
45
79
  }
46
80
  if (step.recordColumns) {
47
81
  snapshotStep.recordColumns = {
48
- ...(step.recordColumns.company ? { company: step.recordColumns.company.map((column) => ({ ...column })) } : {}),
49
- ...(step.recordColumns.contact ? { contact: step.recordColumns.contact.map((column) => ({ ...column })) } : {})
82
+ ...(step.recordColumns.company
83
+ ? {
84
+ company: step.recordColumns.company.map((column: RecordColumnConfig) => ({ ...column }))
85
+ }
86
+ : {}),
87
+ ...(step.recordColumns.contact
88
+ ? {
89
+ contact: step.recordColumns.contact.map((column: RecordColumnConfig) => ({ ...column }))
90
+ }
91
+ : {})
50
92
  }
51
93
  }
52
94
 
53
95
  return snapshotStep
54
96
  })
55
97
  }
56
- }
98
+ }
@@ -1,5 +1,8 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { DEFAULT_ORGANIZATION_MODEL_SALES, LEAD_GEN_PIPELINE_DEFINITIONS } from '../../organization-model/domains/sales'
2
+ // Phase 4 (D8): DEFAULT_ORGANIZATION_MODEL_SALES no longer exported — pipeline data moved to
3
+ // system.content. CRM_PIPELINE_DEFINITION is retained as the Stateful pipeline shape
4
+ // for CRM business logic (separate from the OM content nodes).
5
+ import { CRM_PIPELINE_DEFINITION, LEAD_GEN_PIPELINE_DEFINITIONS } from '../../organization-model/domains/sales'
3
6
  import { ActivityEventSchema } from './activity-events'
4
7
  import { DealStageSchema, TransitionItemRequestSchema } from './api-schemas'
5
8
  import { deriveActions } from './derive-actions'
@@ -106,15 +109,14 @@ describe('ActivityEventSchema', () => {
106
109
  })
107
110
 
108
111
  describe('CRM stage and transition vocabulary contracts', () => {
109
- it('keeps DealStage, DealStageSchema, and the default sales pipeline stages aligned', () => {
110
- const defaultPipeline = DEFAULT_ORGANIZATION_MODEL_SALES.pipelines.find(
111
- (pipeline) => pipeline.id === DEFAULT_ORGANIZATION_MODEL_SALES.defaultPipelineId
112
- )
113
- const defaultSalesStages = defaultPipeline?.stages
114
- .toSorted((left, right) => left.order - right.order)
115
- .map((stage) => stage.id)
116
-
117
- expect(defaultSalesStages).toEqual([...DEAL_STAGES])
112
+ // Phase 4 (D8): DEFAULT_ORGANIZATION_MODEL_SALES.pipelines removed.
113
+ // CRM_PIPELINE_DEFINITION (StatefulPipelineDefinition) is the retained Stateful-trait
114
+ // pipeline shape used by CRM business logic. stageKey → DealStageSchema alignment
115
+ // is now verified against CRM_PIPELINE_DEFINITION.stages[*].stageKey.
116
+ it('keeps DealStage, DealStageSchema, and CRM_PIPELINE_DEFINITION stages aligned', () => {
117
+ const crmStageKeys = CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)
118
+
119
+ expect(crmStageKeys).toEqual([...DEAL_STAGES])
118
120
  expect(DealStageSchema.options).toEqual([...DEAL_STAGES])
119
121
  })
120
122
 
@@ -122,7 +124,7 @@ describe('CRM stage and transition vocabulary contracts', () => {
122
124
  for (const stageKey of DEAL_STAGES) {
123
125
  expect(
124
126
  TransitionItemRequestSchema.safeParse({
125
- pipelineKey: DEFAULT_ORGANIZATION_MODEL_SALES.defaultPipelineId,
127
+ pipelineKey: CRM_PIPELINE_DEFINITION.pipelineKey,
126
128
  stageKey,
127
129
  stateKey: null,
128
130
  expectedUpdatedAt: '2026-04-27T12:34:56.000Z'
@@ -61,6 +61,8 @@ export {
61
61
  ListStageCountsSchema,
62
62
  ListTelemetrySchema,
63
63
  ListIdParamsSchema,
64
+ ListReadQuerySchema,
65
+ GetListQuerySchema,
64
66
  CreateListRequestSchema,
65
67
  UpdateListRequestSchema,
66
68
  UpdateListStatusRequestSchema,
@@ -70,7 +72,11 @@ export {
70
72
  AddContactsToListRequestSchema,
71
73
  RecordListExecutionRequestSchema,
72
74
  AcqListResponseSchema,
75
+ AcqListDetailResponseSchema,
73
76
  AcqListListResponseSchema,
77
+ AcqListDealRefSchema,
78
+ AcqListLineageSchema,
79
+ AcqListStatusResponseSchema,
74
80
  ListTelemetryResponseSchema,
75
81
  ListTelemetryListResponseSchema,
76
82
  ListExecutionSummarySchema,
@@ -144,6 +150,8 @@ export {
144
150
  type ListStageCountsInput,
145
151
  type ListTelemetryInput,
146
152
  type ListIdParams,
153
+ type ListReadQuery,
154
+ type GetListQuery,
147
155
  type CreateListRequest,
148
156
  type UpdateListRequest,
149
157
  type UpdateListStatusRequest,
@@ -153,7 +161,11 @@ export {
153
161
  type AddContactsToListRequest,
154
162
  type RecordListExecutionRequest,
155
163
  type AcqListResponse,
164
+ type AcqListDetailResponse,
156
165
  type AcqListListResponse,
166
+ type AcqListDealRef,
167
+ type AcqListLineage,
168
+ type AcqListStatusResponse,
157
169
  type ListTelemetryResponse,
158
170
  type ListTelemetryListResponse,
159
171
  type ListExecutionSummaryInput,
@@ -1,5 +1,9 @@
1
1
  import type { Database } from '../../supabase/database.types'
2
- import type { Capability, CredentialRequirement, RecordColumnConfig } from '../../organization-model/domains/prospecting'
2
+ import type {
3
+ ActionRegistry,
4
+ CredentialRequirement,
5
+ RecordColumnConfig
6
+ } from '../../organization-model/domains/prospecting'
3
7
  import type { LEAD_GEN_STAGE_CATALOG } from '../../organization-model/domains/sales'
4
8
  import type { PipelineStage, ProcessingStageStatus } from './api-schemas'
5
9
 
@@ -52,7 +56,7 @@ export interface WebPost {
52
56
  }
53
57
 
54
58
  export type LeadGenStageKey = (typeof LEAD_GEN_STAGE_CATALOG)[keyof typeof LEAD_GEN_STAGE_CATALOG]['key']
55
- export type LeadGenCapabilityKey = Capability['id']
59
+ export type LeadGenActionKey = ActionRegistry[number]['id']
56
60
 
57
61
  export interface ProcessingStateEntry {
58
62
  status: ProcessingStageStatus
@@ -172,7 +176,7 @@ export interface BuildPlanSnapshotStep {
172
176
  recordSourceStageKey?: string
173
177
  dependsOn?: string[]
174
178
  dependencyMode: BuildPlanSnapshotDependencyMode
175
- capabilityKey: string
179
+ actionKey: string
176
180
  defaultBatchSize: number
177
181
  maxBatchSize: number
178
182
  recordColumns?: Partial<Record<BuildPlanSnapshotPrimaryEntity, RecordColumnConfig[]>>
@@ -0,0 +1,115 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ ClientDetailResponseSchema,
4
+ ClientListResponseSchema,
5
+ ClientStatusResponseSchema,
6
+ ListClientsQuerySchema
7
+ } from './api-schemas'
8
+
9
+ const VALID_UUID = '00000000-0000-4000-8000-000000000001'
10
+ const ISO_TS = '2026-05-08T00:00:00.000Z'
11
+
12
+ describe('client API schemas', () => {
13
+ it('coerces list pagination and accepts status/search filters', () => {
14
+ const result = ListClientsQuerySchema.safeParse({
15
+ status: 'active',
16
+ search: 'Acme',
17
+ limit: '10',
18
+ offset: '20'
19
+ })
20
+
21
+ expect(result.success).toBe(true)
22
+ if (result.success) {
23
+ expect(result.data).toEqual({
24
+ status: 'active',
25
+ search: 'Acme',
26
+ limit: 10,
27
+ offset: 20
28
+ })
29
+ }
30
+ })
31
+
32
+ it('rejects unknown list query fields', () => {
33
+ expect(ListClientsQuerySchema.safeParse({ includeDeals: true }).success).toBe(false)
34
+ })
35
+
36
+ it('accepts client list responses', () => {
37
+ expect(
38
+ ClientListResponseSchema.safeParse({
39
+ data: [
40
+ {
41
+ id: VALID_UUID,
42
+ organizationId: VALID_UUID,
43
+ name: 'Acme',
44
+ status: 'active',
45
+ sourceDealId: null,
46
+ primaryCompanyId: null,
47
+ primaryContactId: null,
48
+ convertedAt: null,
49
+ metadata: {},
50
+ createdAt: ISO_TS,
51
+ updatedAt: ISO_TS
52
+ }
53
+ ],
54
+ total: 1,
55
+ limit: 50,
56
+ offset: 0
57
+ }).success
58
+ ).toBe(true)
59
+ })
60
+
61
+ it('accepts client detail lineage refs', () => {
62
+ expect(
63
+ ClientDetailResponseSchema.safeParse({
64
+ id: VALID_UUID,
65
+ organizationId: VALID_UUID,
66
+ name: 'Acme',
67
+ status: 'onboarding',
68
+ sourceDealId: VALID_UUID,
69
+ primaryCompanyId: VALID_UUID,
70
+ primaryContactId: VALID_UUID,
71
+ convertedAt: ISO_TS,
72
+ metadata: { source: 'closed_won' },
73
+ createdAt: ISO_TS,
74
+ updatedAt: ISO_TS,
75
+ lineage: {
76
+ deals: [
77
+ {
78
+ id: VALID_UUID,
79
+ contactEmail: 'lead@example.com',
80
+ stageKey: 'closed_won',
81
+ stateKey: null,
82
+ updatedAt: ISO_TS
83
+ }
84
+ ],
85
+ projects: [{ id: VALID_UUID, name: 'Delivery', kind: 'client_engagement', status: 'active', updatedAt: ISO_TS }],
86
+ companies: [{ id: VALID_UUID, name: 'Acme Co', domain: 'acme.test', status: 'active' }],
87
+ contacts: [
88
+ {
89
+ id: VALID_UUID,
90
+ email: 'lead@example.com',
91
+ firstName: 'Ada',
92
+ lastName: null,
93
+ title: 'CEO',
94
+ companyId: VALID_UUID,
95
+ status: 'active'
96
+ }
97
+ ]
98
+ }
99
+ }).success
100
+ ).toBe(true)
101
+ })
102
+
103
+ it('accepts client portfolio status responses', () => {
104
+ expect(
105
+ ClientStatusResponseSchema.safeParse({
106
+ totalClients: 1,
107
+ byStatus: { active: 1, onboarding: 0, paused: 0, completed: 0, churned: 0 },
108
+ linkedDeals: 1,
109
+ linkedProjects: 1,
110
+ linkedCompanies: 1,
111
+ linkedContacts: 1
112
+ }).success
113
+ ).toBe(true)
114
+ })
115
+ })
@@ -0,0 +1,158 @@
1
+ import { z } from 'zod'
2
+ import { UuidSchema } from '../../platform/utils/validation'
3
+
4
+ export const ClientStatusSchema = z.enum(['active', 'onboarding', 'paused', 'completed', 'churned'])
5
+
6
+ export const ClientIdParamsSchema = z
7
+ .object({
8
+ clientId: UuidSchema
9
+ })
10
+ .strict()
11
+
12
+ export const ListClientsQuerySchema = z
13
+ .object({
14
+ status: ClientStatusSchema.optional(),
15
+ search: z.string().trim().min(1).max(255).optional(),
16
+ limit: z.coerce.number().int().min(1).max(100).default(50),
17
+ offset: z.coerce.number().int().min(0).default(0)
18
+ })
19
+ .strict()
20
+
21
+ export const ClientRefSchema = z.object({
22
+ id: z.string(),
23
+ name: z.string(),
24
+ status: ClientStatusSchema
25
+ })
26
+
27
+ export const ClientResponseSchema = z.object({
28
+ id: z.string(),
29
+ organizationId: z.string(),
30
+ name: z.string(),
31
+ status: ClientStatusSchema,
32
+ sourceDealId: z.string().nullable(),
33
+ primaryCompanyId: z.string().nullable(),
34
+ primaryContactId: z.string().nullable(),
35
+ convertedAt: z.string().nullable(),
36
+ metadata: z.record(z.string(), z.unknown()),
37
+ createdAt: z.string(),
38
+ updatedAt: z.string()
39
+ })
40
+
41
+ export const ClientDealRefSchema = z.object({
42
+ id: z.string(),
43
+ contactEmail: z.string(),
44
+ stageKey: z.string().nullable(),
45
+ stateKey: z.string().nullable(),
46
+ updatedAt: z.string()
47
+ })
48
+
49
+ export const ClientProjectRefSchema = z.object({
50
+ id: z.string(),
51
+ name: z.string(),
52
+ kind: z.string(),
53
+ status: z.string(),
54
+ updatedAt: z.string()
55
+ })
56
+
57
+ export const ClientCompanyRefSchema = z.object({
58
+ id: z.string(),
59
+ name: z.string(),
60
+ domain: z.string().nullable(),
61
+ status: z.string()
62
+ })
63
+
64
+ export const ClientContactRefSchema = z.object({
65
+ id: z.string(),
66
+ email: z.string(),
67
+ firstName: z.string().nullable(),
68
+ lastName: z.string().nullable(),
69
+ title: z.string().nullable(),
70
+ companyId: z.string().nullable(),
71
+ status: z.string()
72
+ })
73
+
74
+ export const ClientLineageSchema = z.object({
75
+ deals: z.array(ClientDealRefSchema),
76
+ projects: z.array(ClientProjectRefSchema),
77
+ companies: z.array(ClientCompanyRefSchema),
78
+ contacts: z.array(ClientContactRefSchema)
79
+ })
80
+
81
+ export const ClientDetailResponseSchema = ClientResponseSchema.extend({
82
+ lineage: ClientLineageSchema
83
+ })
84
+
85
+ export const ClientListResponseSchema = z.object({
86
+ data: z.array(ClientResponseSchema),
87
+ total: z.number().int().min(0),
88
+ limit: z.number().int().min(1),
89
+ offset: z.number().int().min(0)
90
+ })
91
+
92
+ export const ClientStatusResponseSchema = z.object({
93
+ totalClients: z.number().int().min(0),
94
+ byStatus: z.record(ClientStatusSchema, z.number().int().min(0)),
95
+ linkedDeals: z.number().int().min(0),
96
+ linkedProjects: z.number().int().min(0),
97
+ linkedCompanies: z.number().int().min(0),
98
+ linkedContacts: z.number().int().min(0)
99
+ })
100
+
101
+ export const CreateClientRequestSchema = z
102
+ .object({
103
+ name: z.string().trim().min(1).max(255),
104
+ status: ClientStatusSchema.optional(),
105
+ sourceDealId: UuidSchema.nullable().optional(),
106
+ primaryCompanyId: UuidSchema.nullable().optional(),
107
+ primaryContactId: UuidSchema.nullable().optional(),
108
+ metadata: z.record(z.string(), z.unknown()).nullable().optional()
109
+ })
110
+ .strict()
111
+
112
+ export const UpdateClientRequestSchema = z
113
+ .object({
114
+ name: z.string().trim().min(1).max(255).optional(),
115
+ status: ClientStatusSchema.optional(),
116
+ sourceDealId: UuidSchema.nullable().optional(),
117
+ primaryCompanyId: UuidSchema.nullable().optional(),
118
+ primaryContactId: UuidSchema.nullable().optional(),
119
+ metadata: z.record(z.string(), z.unknown()).nullable().optional()
120
+ })
121
+ .strict()
122
+ .refine((data) => Object.keys(data).length > 0, {
123
+ message: 'At least one field must be provided'
124
+ })
125
+
126
+ export const ClientSchemas = {
127
+ ClientStatus: ClientStatusSchema,
128
+ ClientIdParams: ClientIdParamsSchema,
129
+ ListClientsQuery: ListClientsQuerySchema,
130
+ ClientRef: ClientRefSchema,
131
+ ClientResponse: ClientResponseSchema,
132
+ ClientDealRef: ClientDealRefSchema,
133
+ ClientProjectRef: ClientProjectRefSchema,
134
+ ClientCompanyRef: ClientCompanyRefSchema,
135
+ ClientContactRef: ClientContactRefSchema,
136
+ ClientLineage: ClientLineageSchema,
137
+ ClientDetailResponse: ClientDetailResponseSchema,
138
+ ClientListResponse: ClientListResponseSchema,
139
+ ClientStatusResponse: ClientStatusResponseSchema,
140
+ CreateClientRequest: CreateClientRequestSchema,
141
+ UpdateClientRequest: UpdateClientRequestSchema
142
+ }
143
+
144
+ export type ClientStatus = z.infer<typeof ClientStatusSchema>
145
+ export type ClientIdParams = z.infer<typeof ClientIdParamsSchema>
146
+ export type ListClientsQuery = z.infer<typeof ListClientsQuerySchema>
147
+ export type ClientRef = z.infer<typeof ClientRefSchema>
148
+ export type ClientResponse = z.infer<typeof ClientResponseSchema>
149
+ export type ClientDealRef = z.infer<typeof ClientDealRefSchema>
150
+ export type ClientProjectRef = z.infer<typeof ClientProjectRefSchema>
151
+ export type ClientCompanyRef = z.infer<typeof ClientCompanyRefSchema>
152
+ export type ClientContactRef = z.infer<typeof ClientContactRefSchema>
153
+ export type ClientLineage = z.infer<typeof ClientLineageSchema>
154
+ export type ClientDetailResponse = z.infer<typeof ClientDetailResponseSchema>
155
+ export type ClientListResponse = z.infer<typeof ClientListResponseSchema>
156
+ export type ClientStatusResponse = z.infer<typeof ClientStatusResponseSchema>
157
+ export type CreateClientRequest = z.infer<typeof CreateClientRequestSchema>
158
+ export type UpdateClientRequest = z.infer<typeof UpdateClientRequestSchema>
@@ -0,0 +1 @@
1
+ export * from './api-schemas'
@@ -42,6 +42,10 @@ export {
42
42
  DealLookupResponseSchema,
43
43
  ConversationMessageSchema,
44
44
  DealConversationSchema,
45
+ DealLineageListRefSchema,
46
+ DealLineageProjectRefSchema,
47
+ DealLineageClientRefSchema,
48
+ DealLineageSchema,
45
49
  DealDetailResponseSchema,
46
50
  DealNoteResponseSchema,
47
51
  DealNoteListResponseSchema,
@@ -71,6 +75,10 @@ export {
71
75
  type DealLookupItem,
72
76
  type DealLookupResponse,
73
77
  type ConversationMessage,
78
+ type DealLineageListRef,
79
+ type DealLineageProjectRef,
80
+ type DealLineageClientRef,
81
+ type DealLineage,
74
82
  type DealDetailResponse,
75
83
  type DealNoteResponse,
76
84
  type DealNoteListResponse,
@@ -5,8 +5,11 @@
5
5
  * Note: PDF has its own subpath exports (./pdf, ./pdf/browser, ./pdf/server, ./pdf/sections)
6
6
  */
7
7
 
8
- // Acquisition types (lead database domain types)
9
- export * from './acquisition/index'
8
+ // Acquisition types (lead database domain types)
9
+ export * from './acquisition/index'
10
+
11
+ // Client hub schemas
12
+ export * from './clients/index'
10
13
 
11
14
  // SEO types (seo_pages, seo_metrics, chart types)
12
15
  export * from './seo/index'
@@ -75,6 +75,24 @@ export interface ProjectDetail extends ProjectRow {
75
75
  milestones: MilestoneRow[]
76
76
  tasks: TaskRow[]
77
77
  company: { id: string; name: string; domain: string | null } | null
78
+ deal: ProjectSourceDealRef | null
79
+ client: ProjectClientRef | null
80
+ }
81
+
82
+ export interface ProjectSourceDealRef {
83
+ id: string
84
+ clientId?: string | null
85
+ contactEmail: string
86
+ stageKey: string | null
87
+ stateKey: string | null
88
+ sourceListId: string | null
89
+ updatedAt: string
90
+ }
91
+
92
+ export interface ProjectClientRef {
93
+ id: string
94
+ name: string
95
+ status: string
78
96
  }
79
97
 
80
98
  export interface MilestoneWithTasks extends MilestoneRow {
@@ -104,6 +122,7 @@ export interface Project {
104
122
  status: ProjectStatus
105
123
  description: string | null
106
124
  dealId: string | null
125
+ clientId: string | null
107
126
  clientCompanyId: string | null
108
127
  startDate: string | null
109
128
  targetEndDate: string | null
@@ -11,10 +11,11 @@ export function createPlatformToolAgent(): AgentDefinition {
11
11
  config: {
12
12
  resourceId: 'platform-tool-agent',
13
13
  name: 'Platform Tool Agent',
14
- description: 'Test agent with platform tools for approval workflows',
15
- systemPrompt: 'You help with approvals and notifications',
16
- type: 'agent',
17
- status: 'dev',
14
+ description: 'Test agent with platform tools for approval workflows',
15
+ systemPrompt: 'You help with approvals and notifications',
16
+ type: 'agent',
17
+ kind: 'utility',
18
+ status: 'dev',
18
19
  version: '1.0.0'
19
20
  },
20
21
  contract: {
@@ -68,10 +69,11 @@ export function createKnowledgeMapAgent(): AgentDefinition {
68
69
  config: {
69
70
  resourceId: 'knowledge-agent',
70
71
  name: 'Knowledge Map Agent',
71
- description: 'Test agent with knowledge map for dynamic capability loading',
72
- systemPrompt: 'You can load knowledge nodes as needed',
73
- type: 'agent',
74
- status: 'dev',
72
+ description: 'Test agent with knowledge map for dynamic capability loading',
73
+ systemPrompt: 'You can load knowledge nodes as needed',
74
+ type: 'agent',
75
+ kind: 'utility',
76
+ status: 'dev',
75
77
  version: '1.0.0'
76
78
  },
77
79
  contract: {
@@ -19,9 +19,10 @@ describe('Agent Execution', () => {
19
19
 
20
20
  // Helper: Create minimal agent definition with mock provider
21
21
  const createAgentDefinition = (overrides?: Partial<AgentDefinition>): AgentDefinition => {
22
- const config: AgentConfig = {
23
- type: 'agent',
24
- resourceId: 'test-agent',
22
+ const config: AgentConfig = {
23
+ type: 'agent',
24
+ kind: 'utility',
25
+ resourceId: 'test-agent',
25
26
  name: 'Test Agent',
26
27
  description: 'Agent for testing',
27
28
  environment: 'dev',
@@ -165,9 +166,10 @@ describe('Agent Execution', () => {
165
166
 
166
167
  it('respects maxIterations constraint', async () => {
167
168
  const definition = createAgentDefinition({
168
- config: {
169
- type: 'agent',
170
- resourceId: 'test-agent',
169
+ config: {
170
+ type: 'agent',
171
+ kind: 'utility',
172
+ resourceId: 'test-agent',
171
173
  name: 'Test Agent',
172
174
  description: 'Agent for testing',
173
175
  environment: 'dev',
@@ -418,9 +420,10 @@ describe('Agent Execution', () => {
418
420
 
419
421
  it('handles maxIterations edge case (zero iterations)', async () => {
420
422
  const definition = createAgentDefinition({
421
- config: {
422
- type: 'agent',
423
- resourceId: 'test-agent',
423
+ config: {
424
+ type: 'agent',
425
+ kind: 'utility',
426
+ resourceId: 'test-agent',
424
427
  name: 'Test Agent',
425
428
  description: 'Agent for testing',
426
429
  environment: 'dev',
@@ -442,9 +445,10 @@ describe('Agent Execution', () => {
442
445
 
443
446
  it('handles negative maxIterations', async () => {
444
447
  const definition = createAgentDefinition({
445
- config: {
446
- type: 'agent',
447
- resourceId: 'test-agent',
448
+ config: {
449
+ type: 'agent',
450
+ kind: 'utility',
451
+ resourceId: 'test-agent',
448
452
  name: 'Test Agent',
449
453
  description: 'Agent for testing',
450
454
  environment: 'dev',