@elevasis/core 0.13.0 → 0.14.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 (41) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +9 -2
  3. package/dist/organization-model/index.d.ts +1 -1
  4. package/dist/organization-model/index.js +9 -2
  5. package/dist/test-utils/index.d.ts +463 -377
  6. package/dist/test-utils/index.js +9 -2
  7. package/package.json +1 -1
  8. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +2324 -0
  9. package/src/business/acquisition/activity-events.test.ts +250 -0
  10. package/src/business/acquisition/activity-events.ts +7 -65
  11. package/src/business/acquisition/api-schemas.test.ts +1180 -0
  12. package/src/business/acquisition/api-schemas.ts +1075 -859
  13. package/src/business/acquisition/crm-state-actions.test.ts +160 -0
  14. package/src/business/acquisition/derive-actions.test.ts +518 -0
  15. package/src/business/acquisition/derive-actions.ts +103 -90
  16. package/src/business/acquisition/index.ts +149 -111
  17. package/src/business/acquisition/stateful.ts +30 -0
  18. package/src/business/acquisition/types.ts +44 -77
  19. package/src/execution/engine/index.ts +437 -434
  20. package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +363 -360
  21. package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.test.ts +162 -186
  22. package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.test.ts +316 -338
  23. package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-adapter.ts +204 -210
  24. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.test.ts +88 -0
  25. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.ts +141 -134
  26. package/src/execution/engine/tools/integration/server/adapters/resend/fetch/utils/types.ts +76 -75
  27. package/src/execution/engine/tools/integration/service.test.ts +34 -9
  28. package/src/execution/engine/tools/integration/service.ts +6 -3
  29. package/src/execution/engine/tools/lead-service-types.ts +945 -888
  30. package/src/execution/engine/tools/platform/acquisition/types.ts +266 -260
  31. package/src/execution/engine/tools/registry.ts +701 -699
  32. package/src/execution/engine/tools/tool-maps.ts +816 -791
  33. package/src/execution/engine/workflow/types.ts +11 -0
  34. package/src/organization-model/contracts.ts +4 -4
  35. package/src/organization-model/domains/navigation.ts +62 -62
  36. package/src/organization-model/domains/sales.ts +272 -0
  37. package/src/organization-model/published.ts +21 -21
  38. package/src/organization-model/resolve.ts +21 -8
  39. package/src/platform/constants/versions.ts +1 -1
  40. package/src/reference/_generated/contracts.md +2324 -0
  41. package/src/supabase/database.types.ts +2958 -2886
@@ -82,6 +82,17 @@ export interface WorkflowDefinition {
82
82
  * If provided, workflow appears in Execution Runner UI
83
83
  */
84
84
  interface?: ExecutionInterface
85
+
86
+ /**
87
+ * Lead-gen processing stage this workflow implements (optional).
88
+ * Must match a key in the platform lead-gen stage catalog.
89
+ * Used by org-os graph derivation to surface workflow→stage edges and
90
+ * by pipeline_config validation to confirm each catalog stage has an
91
+ * implementing workflow before a list is activated.
92
+ *
93
+ * Example: stageImplemented: 'verified' on the email-verification workflow.
94
+ */
95
+ stageImplemented?: string
85
96
  }
86
97
 
87
98
  /**
@@ -9,7 +9,7 @@ export const MONITORING_FEATURE_ID = 'monitoring' as const
9
9
  export const SETTINGS_FEATURE_ID = 'settings' as const
10
10
  export const SEO_FEATURE_ID = 'seo' as const
11
11
 
12
- export const SALES_PIPELINE_SURFACE_ID = 'crm.pipeline' as const
13
- export const PROSPECTING_LISTS_SURFACE_ID = 'lead-gen.lists' as const
14
- export const OPERATIONS_COMMAND_VIEW_SURFACE_ID = 'operations.command-view' as const
15
- export const SETTINGS_ROLES_SURFACE_ID = 'settings.roles' as const
12
+ export const SALES_PIPELINE_SURFACE_ID = 'crm.pipeline' as const
13
+ export const PROSPECTING_LISTS_SURFACE_ID = 'lead-gen.lists' as const
14
+ export const OPERATIONS_COMMAND_VIEW_SURFACE_ID = 'operations.command-view' as const
15
+ export const SETTINGS_ROLES_SURFACE_ID = 'settings.roles' as const
@@ -1,11 +1,11 @@
1
1
  import { z } from 'zod'
2
- import {
3
- OPERATIONS_COMMAND_VIEW_SURFACE_ID,
4
- PROJECTS_VIEW_CAPABILITY_ID,
5
- PROJECTS_FEATURE_ID,
6
- PROJECTS_INDEX_SURFACE_ID,
7
- SETTINGS_ROLES_SURFACE_ID
8
- } from '../contracts'
2
+ import {
3
+ OPERATIONS_COMMAND_VIEW_SURFACE_ID,
4
+ PROJECTS_VIEW_CAPABILITY_ID,
5
+ PROJECTS_FEATURE_ID,
6
+ PROJECTS_INDEX_SURFACE_ID,
7
+ SETTINGS_ROLES_SURFACE_ID
8
+ } from '../contracts'
9
9
  import { DescriptionSchema, IconNameSchema, LabelSchema, ModelIdSchema, PathSchema, ReferenceIdsSchema } from './shared'
10
10
 
11
11
  export const SurfaceTypeSchema = z.enum(['page', 'dashboard', 'graph', 'detail', 'list', 'settings'])
@@ -15,10 +15,10 @@ export const SurfaceDefinitionSchema = z.object({
15
15
  label: LabelSchema,
16
16
  path: PathSchema,
17
17
  surfaceType: SurfaceTypeSchema,
18
- description: DescriptionSchema.optional(),
19
- enabled: z.boolean().default(true),
20
- devOnly: z.boolean().optional(),
21
- icon: IconNameSchema.optional(),
18
+ description: DescriptionSchema.optional(),
19
+ enabled: z.boolean().default(true),
20
+ devOnly: z.boolean().optional(),
21
+ icon: IconNameSchema.optional(),
22
22
  featureId: ModelIdSchema.optional(),
23
23
  featureIds: ReferenceIdsSchema,
24
24
  entityIds: ReferenceIdsSchema,
@@ -92,16 +92,16 @@ export const DEFAULT_ORGANIZATION_MODEL_NAVIGATION: z.infer<typeof OrganizationM
92
92
  resourceIds: [],
93
93
  capabilityIds: [PROJECTS_VIEW_CAPABILITY_ID]
94
94
  },
95
- {
96
- id: OPERATIONS_COMMAND_VIEW_SURFACE_ID,
97
- label: 'Command View',
98
- path: '/operations/command-view',
99
- surfaceType: 'graph',
100
- enabled: true,
101
- devOnly: true,
102
- featureId: 'operations',
103
- featureIds: ['operations'],
104
- entityIds: [],
95
+ {
96
+ id: OPERATIONS_COMMAND_VIEW_SURFACE_ID,
97
+ label: 'Command View',
98
+ path: '/operations/command-view',
99
+ surfaceType: 'graph',
100
+ enabled: true,
101
+ devOnly: true,
102
+ featureId: 'operations',
103
+ featureIds: ['operations'],
104
+ entityIds: [],
105
105
  resourceIds: [],
106
106
  capabilityIds: ['operations.command-view']
107
107
  },
@@ -261,45 +261,45 @@ export const DEFAULT_ORGANIZATION_MODEL_NAVIGATION: z.infer<typeof OrganizationM
261
261
  resourceIds: [],
262
262
  capabilityIds: []
263
263
  },
264
- {
265
- id: 'settings.appearance',
266
- label: 'Appearance',
267
- path: '/settings/appearance',
264
+ {
265
+ id: 'settings.appearance',
266
+ label: 'Appearance',
267
+ path: '/settings/appearance',
268
+ surfaceType: 'settings',
269
+ enabled: true,
270
+ featureId: 'settings',
271
+ featureIds: ['settings'],
272
+ entityIds: [],
273
+ resourceIds: [],
274
+ capabilityIds: []
275
+ },
276
+ {
277
+ id: SETTINGS_ROLES_SURFACE_ID,
278
+ label: 'My Roles',
279
+ path: '/settings/roles',
268
280
  surfaceType: 'settings',
269
281
  enabled: true,
270
282
  featureId: 'settings',
271
283
  featureIds: ['settings'],
272
284
  entityIds: [],
273
- resourceIds: [],
274
- capabilityIds: []
275
- },
276
- {
277
- id: SETTINGS_ROLES_SURFACE_ID,
278
- label: 'My Roles',
279
- path: '/settings/roles',
280
- surfaceType: 'settings',
281
- enabled: true,
282
- featureId: 'settings',
283
- featureIds: ['settings'],
284
- entityIds: [],
285
- resourceIds: [],
286
- capabilityIds: []
287
- },
288
- {
289
- id: 'settings.organization',
290
- label: 'Organization',
291
- path: '/settings/organization',
285
+ resourceIds: [],
286
+ capabilityIds: []
287
+ },
288
+ {
289
+ id: 'settings.organization',
290
+ label: 'Organization',
291
+ path: '/settings/organization',
292
292
  surfaceType: 'settings',
293
293
  enabled: true,
294
294
  featureId: 'settings',
295
295
  featureIds: ['settings'],
296
296
  entityIds: [],
297
- resourceIds: [],
298
- capabilityIds: []
299
- },
300
- {
301
- id: 'settings.credentials',
302
- label: 'Credentials',
297
+ resourceIds: [],
298
+ capabilityIds: []
299
+ },
300
+ {
301
+ id: 'settings.credentials',
302
+ label: 'Credentials',
303
303
  path: '/settings/credentials',
304
304
  surfaceType: 'settings',
305
305
  enabled: true,
@@ -355,12 +355,12 @@ export const DEFAULT_ORGANIZATION_MODEL_NAVIGATION: z.infer<typeof OrganizationM
355
355
  },
356
356
  {
357
357
  id: 'primary-operations',
358
- label: 'Operations',
359
- placement: 'primary',
360
- surfaceIds: [
361
- OPERATIONS_COMMAND_VIEW_SURFACE_ID,
362
- 'operations.overview',
363
- 'operations.resources',
358
+ label: 'Operations',
359
+ placement: 'primary',
360
+ surfaceIds: [
361
+ OPERATIONS_COMMAND_VIEW_SURFACE_ID,
362
+ 'operations.overview',
363
+ 'operations.resources',
364
364
  'operations.command-queue',
365
365
  'operations.sessions',
366
366
  'operations.task-scheduler'
@@ -385,12 +385,12 @@ export const DEFAULT_ORGANIZATION_MODEL_NAVIGATION: z.infer<typeof OrganizationM
385
385
  label: 'Settings',
386
386
  placement: 'bottom',
387
387
  surfaceIds: [
388
- 'settings.account',
389
- 'settings.appearance',
390
- SETTINGS_ROLES_SURFACE_ID,
391
- 'settings.organization',
392
- 'settings.credentials',
393
- 'settings.api-keys',
388
+ 'settings.account',
389
+ 'settings.appearance',
390
+ SETTINGS_ROLES_SURFACE_ID,
391
+ 'settings.organization',
392
+ 'settings.credentials',
393
+ 'settings.api-keys',
394
394
  'settings.webhooks',
395
395
  'settings.deployments'
396
396
  ]
@@ -92,3 +92,275 @@ export const DEFAULT_ORGANIZATION_MODEL_SALES: z.infer<typeof OrganizationModelS
92
92
  }
93
93
  ]
94
94
  }
95
+
96
+ // ============================================================================
97
+ // Lead-Gen Stateful Pipeline Definitions (Decision N8, Wave 4)
98
+ //
99
+ // Defines the (pipeline_key, stage_key, state_key) vocabulary for the three
100
+ // entities that carry the Stateful trait via Track B:
101
+ // - acq_lists (pipeline_key='lead-gen', stage_key='lifecycle')
102
+ // - acq_list_members (pipeline_key='lead-gen', stages: outreach / prospecting / qualification)
103
+ // - acq_list_companies (pipeline_key='lead-gen', stages: outreach / prospecting / qualification)
104
+ //
105
+ // DB columns (pipeline_key, stage_key, state_key) remain free-form text with no
106
+ // CHECK constraint — new states can be introduced without a migration (Decision N8).
107
+ // These definitions are the org-specific source of truth consumed by UI and tooling.
108
+ //
109
+ // State vocabularies sourced from the post-restructure sales tree (W3 canonical):
110
+ // outreach/:
111
+ // - personalized (instantly-personalization.ts → contacts)
112
+ // - uploaded (instantly-upload.ts → contacts)
113
+ // - interested (instantly-reply-handler.ts → contacts, initial reply transition)
114
+ // prospecting/:
115
+ // - populated (apify-acquire.ts, apify-scrape.ts → companies)
116
+ // - extracted (website-extract.ts → companies)
117
+ // - discovered (email-discovery.ts, anymailfinder-enrich.ts → contacts)
118
+ // - verified (email-verification.ts → contacts)
119
+ // qualification/:
120
+ // - qualified (company-qualification.ts → companies)
121
+ //
122
+ // The 'pending' state is the W2 backfill default (coalesce(stage, 'pending')).
123
+ // It is valid in any stage and represents "not yet processed by a workflow step".
124
+ // ============================================================================
125
+
126
+ /** One state within a stage — minimal shape: key + display label. */
127
+ export interface StatefulStateDefinition {
128
+ /** Matches state_key values written by workflow steps. */
129
+ stateKey: string
130
+ label: string
131
+ }
132
+
133
+ /** One stage within a pipeline — has a stage_key and an ordered list of valid states. */
134
+ export interface StatefulStageDefinition {
135
+ /** Matches stage_key values written by workflow steps. */
136
+ stageKey: string
137
+ label: string
138
+ states: StatefulStateDefinition[]
139
+ }
140
+
141
+ /**
142
+ * Pipeline definition for a single entity participating in the Stateful trait.
143
+ * Parallel to acq_deals' pipeline_key concept but structured for lead-gen entities.
144
+ */
145
+ export interface StatefulPipelineDefinition {
146
+ /** Matches pipeline_key values in the database (e.g. 'lead-gen'). */
147
+ pipelineKey: string
148
+ label: string
149
+ /** Entity this pipeline applies to (e.g. 'acq.list', 'acq.list-member', 'acq.list-company'). */
150
+ entityKey: string
151
+ stages: StatefulStageDefinition[]
152
+ }
153
+
154
+ /**
155
+ * Find a pipeline definition by pipeline_key within an array of definitions.
156
+ * Preferred over direct array indexing since the model is array-based (not keyed).
157
+ */
158
+ export function findPipeline(
159
+ definitions: StatefulPipelineDefinition[],
160
+ pipelineKey: string
161
+ ): StatefulPipelineDefinition | undefined {
162
+ return definitions.find((def) => def.pipelineKey === pipelineKey)
163
+ }
164
+
165
+ /** Common states that appear across multiple stages. */
166
+ const PENDING_STATE: StatefulStateDefinition = { stateKey: 'pending', label: 'Pending' }
167
+
168
+ /**
169
+ * Lead-gen pipeline definition for acq_list_members (contacts).
170
+ * Three stages matching the post-restructure sales subdomain tree.
171
+ *
172
+ * Note: members visit outreach and prospecting states depending on which
173
+ * workflow last processed them. stage_key is set per-transition by the workflow.
174
+ */
175
+ export const ACQ_LIST_MEMBERS_LEAD_GEN_PIPELINE: StatefulPipelineDefinition = {
176
+ pipelineKey: 'lead-gen',
177
+ label: 'Lead Generation',
178
+ entityKey: 'acq.list-member',
179
+ stages: [
180
+ {
181
+ stageKey: 'outreach',
182
+ label: 'Outreach',
183
+ states: [
184
+ PENDING_STATE,
185
+ { stateKey: 'personalized', label: 'Personalized' },
186
+ { stateKey: 'uploaded', label: 'Uploaded' },
187
+ { stateKey: 'interested', label: 'Interested' }
188
+ ]
189
+ },
190
+ {
191
+ stageKey: 'prospecting',
192
+ label: 'Prospecting',
193
+ states: [
194
+ PENDING_STATE,
195
+ { stateKey: 'discovered', label: 'Discovered' },
196
+ { stateKey: 'verified', label: 'Verified' }
197
+ ]
198
+ },
199
+ {
200
+ stageKey: 'qualification',
201
+ label: 'Qualification',
202
+ states: [PENDING_STATE]
203
+ }
204
+ ]
205
+ }
206
+
207
+ /**
208
+ * Lead-gen pipeline definition for acq_list_companies.
209
+ * Three stages matching the post-restructure sales subdomain tree.
210
+ *
211
+ * Note: companies visit prospecting and qualification states depending on which
212
+ * workflow last processed them. stage_key is set per-transition by the workflow.
213
+ */
214
+ export const ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE: StatefulPipelineDefinition = {
215
+ pipelineKey: 'lead-gen',
216
+ label: 'Lead Generation',
217
+ entityKey: 'acq.list-company',
218
+ stages: [
219
+ {
220
+ stageKey: 'outreach',
221
+ label: 'Outreach',
222
+ states: [PENDING_STATE]
223
+ },
224
+ {
225
+ stageKey: 'prospecting',
226
+ label: 'Prospecting',
227
+ states: [
228
+ PENDING_STATE,
229
+ { stateKey: 'populated', label: 'Populated' },
230
+ { stateKey: 'extracted', label: 'Extracted' }
231
+ ]
232
+ },
233
+ {
234
+ stageKey: 'qualification',
235
+ label: 'Qualification',
236
+ states: [PENDING_STATE, { stateKey: 'qualified', label: 'Qualified' }]
237
+ }
238
+ ]
239
+ }
240
+
241
+ /**
242
+ * All lead-gen pipeline definitions indexed by entity key.
243
+ * Use findPipeline() to locate a definition by pipeline_key within any of these arrays.
244
+ */
245
+ export const LEAD_GEN_PIPELINE_DEFINITIONS: Record<string, StatefulPipelineDefinition[]> = {
246
+ 'acq.list-member': [ACQ_LIST_MEMBERS_LEAD_GEN_PIPELINE],
247
+ 'acq.list-company': [ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE]
248
+ }
249
+
250
+ // ============================================================================
251
+ // Lead-Gen Stage Catalog (Slice C — flag-based processing model)
252
+ //
253
+ // Canonical set of processing stage keys for the flag-based model that replaces
254
+ // the stateful trait on acq_list_members and acq_list_companies. Each key maps
255
+ // to a flag in the processing_state jsonb column (bare boolean per Decision #7).
256
+ //
257
+ // Sources:
258
+ // ACQ_LIST_MEMBERS_LEAD_GEN_PIPELINE → personalized, uploaded, interested,
259
+ // discovered, verified
260
+ // ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE → populated, extracted, qualified
261
+ // Design plan hint (lead-gen-domain-cleanup.mdx §4) → scraped, enriched
262
+ //
263
+ // Wave 2 will validate pipeline_config.stages[].key against this catalog at
264
+ // list-create/update time. Each lead-gen workflow in operations will declare
265
+ // stageImplemented: '<key>' in its WorkflowDefinition (Slice D-2).
266
+ // ============================================================================
267
+
268
+ /** One entry in the lead-gen stage catalog. */
269
+ export interface LeadGenStageCatalogEntry {
270
+ /** Matches the flag key written into processing_state jsonb (e.g. 'scraped'). */
271
+ key: string
272
+ /** Human-readable label for UI display. */
273
+ label: string
274
+ /** Short description of what this stage represents. */
275
+ description: string
276
+ /** Canonical pipeline order for UI sorting. Lower = earlier in the funnel. */
277
+ order: number
278
+ /** Which entity's processing_state jsonb carries this flag. */
279
+ entity: 'company' | 'contact'
280
+ }
281
+
282
+ /**
283
+ * Canonical lead-gen processing stage catalog.
284
+ * Keys are the flag names written by workflow steps into processing_state jsonb.
285
+ *
286
+ * Ordered roughly by pipeline progression (prospecting → outreach → qualification).
287
+ */
288
+ export const LEAD_GEN_STAGE_CATALOG: Record<string, LeadGenStageCatalogEntry> = {
289
+ // Prospecting — company population
290
+ scraped: {
291
+ key: 'scraped',
292
+ label: 'Scraped',
293
+ description: 'Company was scraped from a source directory (Apify actor run).',
294
+ order: 1,
295
+ entity: 'company'
296
+ },
297
+ populated: {
298
+ key: 'populated',
299
+ label: 'Populated',
300
+ description: 'Company record populated with structured data from scrape results.',
301
+ order: 2,
302
+ entity: 'company'
303
+ },
304
+ extracted: {
305
+ key: 'extracted',
306
+ label: 'Extracted',
307
+ description: 'Website content extracted and parsed for company intelligence.',
308
+ order: 3,
309
+ entity: 'company'
310
+ },
311
+ enriched: {
312
+ key: 'enriched',
313
+ label: 'Enriched',
314
+ description: 'Company or contact enriched with third-party data (e.g. Tomba, Anymailfinder).',
315
+ order: 4,
316
+ entity: 'company'
317
+ },
318
+
319
+ // Prospecting — contact discovery
320
+ discovered: {
321
+ key: 'discovered',
322
+ label: 'Discovered',
323
+ description: 'Contact email address discovered via an email-discovery workflow.',
324
+ order: 5,
325
+ entity: 'contact'
326
+ },
327
+ verified: {
328
+ key: 'verified',
329
+ label: 'Verified',
330
+ description: 'Contact email address verified as deliverable (email verification workflow).',
331
+ order: 6,
332
+ entity: 'contact'
333
+ },
334
+
335
+ // Qualification
336
+ qualified: {
337
+ key: 'qualified',
338
+ label: 'Qualified',
339
+ description: 'Company passed the ICP qualification rubric (company-qualification workflow).',
340
+ order: 7,
341
+ entity: 'company'
342
+ },
343
+
344
+ // Outreach
345
+ personalized: {
346
+ key: 'personalized',
347
+ label: 'Personalized',
348
+ description: 'Outreach message personalized for the contact (Instantly personalization workflow).',
349
+ order: 8,
350
+ entity: 'contact'
351
+ },
352
+ uploaded: {
353
+ key: 'uploaded',
354
+ label: 'Uploaded',
355
+ description: 'Contact uploaded to an Instantly campaign for outreach.',
356
+ order: 9,
357
+ entity: 'contact'
358
+ },
359
+ interested: {
360
+ key: 'interested',
361
+ label: 'Interested',
362
+ description: 'Contact replied with a positive signal (Instantly reply-handler transition).',
363
+ order: 10,
364
+ entity: 'contact'
365
+ }
366
+ }
@@ -1,6 +1,6 @@
1
1
  export { OrganizationModelSchema } from './schema'
2
- export { FeatureSchema, NodeIdPathSchema, NodeIdStringSchema, UiPositionSchema } from './domains/features'
3
- export { LinkSchema } from './graph/link'
2
+ export { FeatureSchema, NodeIdPathSchema, NodeIdStringSchema, UiPositionSchema } from './domains/features'
3
+ export { LinkSchema } from './graph/link'
4
4
  export { TechStackEntrySchema } from './domains/shared'
5
5
  export {
6
6
  PROJECTS_FEATURE_ID,
@@ -11,12 +11,12 @@ export {
11
11
  OPERATIONS_FEATURE_ID,
12
12
  MONITORING_FEATURE_ID,
13
13
  SETTINGS_FEATURE_ID,
14
- SEO_FEATURE_ID,
15
- SALES_PIPELINE_SURFACE_ID,
16
- PROSPECTING_LISTS_SURFACE_ID,
17
- OPERATIONS_COMMAND_VIEW_SURFACE_ID,
18
- SETTINGS_ROLES_SURFACE_ID
19
- } from './contracts'
14
+ SEO_FEATURE_ID,
15
+ SALES_PIPELINE_SURFACE_ID,
16
+ PROSPECTING_LISTS_SURFACE_ID,
17
+ OPERATIONS_COMMAND_VIEW_SURFACE_ID,
18
+ SETTINGS_ROLES_SURFACE_ID
19
+ } from './contracts'
20
20
  export { DEFAULT_ORGANIZATION_MODEL } from './defaults'
21
21
  export {
22
22
  DEFAULT_ORGANIZATION_MODEL_STATUSES,
@@ -57,25 +57,25 @@ export type {
57
57
  OrganizationModelCustomerFirmographics,
58
58
  OrganizationModelCustomers,
59
59
  OrganizationModelCustomerSegment,
60
- OrganizationModelFeature,
61
- OrganizationModelGoals,
62
- OrganizationModelKeyResult,
63
- OrganizationModelObjective,
60
+ OrganizationModelFeature,
61
+ OrganizationModelGoals,
62
+ OrganizationModelKeyResult,
63
+ OrganizationModelObjective,
64
64
  OrganizationModelOfferings,
65
65
  OrganizationModelOperationEntry,
66
66
  OrganizationModelOperationSemanticClass,
67
67
  OrganizationModelOperations,
68
- OrganizationModelPricingModel,
69
- OrganizationModelProduct,
70
- OrganizationModelRole,
68
+ OrganizationModelPricingModel,
69
+ OrganizationModelProduct,
70
+ OrganizationModelRole,
71
71
  OrganizationModelTechStackEntry,
72
72
  OrganizationModelRoles,
73
- OrganizationModelStatuses,
74
- OrganizationModelStatusEntry,
75
- OrganizationModelStatusSemanticClass,
76
- NodeIdPath,
77
- NodeIdString
78
- } from './types'
73
+ OrganizationModelStatuses,
74
+ OrganizationModelStatusEntry,
75
+ OrganizationModelStatusSemanticClass,
76
+ NodeIdPath,
77
+ NodeIdString
78
+ } from './types'
79
79
 
80
80
  export type {
81
81
  FoundationBranding,
@@ -31,11 +31,24 @@ function deepMerge<T>(base: T, override: DeepPartial<T> | undefined): T {
31
31
  return result as T
32
32
  }
33
33
 
34
- export function defineOrganizationModel<T extends DeepPartial<OrganizationModel>>(model: T): T {
35
- return model
36
- }
37
-
38
- export function resolveOrganizationModel(override?: DeepPartial<OrganizationModel>): OrganizationModel {
39
- const merged = deepMerge(DEFAULT_ORGANIZATION_MODEL, override)
40
- return OrganizationModelSchema.parse(merged)
41
- }
34
+ export function defineOrganizationModel<T extends DeepPartial<OrganizationModel>>(model: T): T {
35
+ return model
36
+ }
37
+
38
+ export function resolveOrganizationModel(
39
+ override?: DeepPartial<OrganizationModel>,
40
+ organizationId?: string
41
+ ): OrganizationModel {
42
+ const merged = deepMerge(DEFAULT_ORGANIZATION_MODEL, override)
43
+ const model = OrganizationModelSchema.parse(merged)
44
+
45
+ if (!model.sales?.pipelines || model.sales.pipelines.length === 0) {
46
+ const orgLabel = organizationId ? `Organization ${organizationId}` : 'Organization'
47
+ throw new Error(
48
+ `${orgLabel} has no sales pipeline configuration. ` +
49
+ `This indicates an incomplete provisioning state. Run org provisioning to seed defaults.`
50
+ )
51
+ }
52
+
53
+ return model
54
+ }
@@ -1,3 +1,3 @@
1
1
  export const VERSION = {
2
- CURRENT: '1.6.17'
2
+ CURRENT: '1.7.0'
3
3
  }