@elevasis/core 0.28.0 → 0.30.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 (36) hide show
  1. package/dist/auth/index.d.ts +5289 -0
  2. package/dist/auth/index.js +595 -0
  3. package/dist/index.d.ts +11 -11
  4. package/dist/knowledge/index.d.ts +1 -1
  5. package/dist/organization-model/index.d.ts +11 -11
  6. package/dist/test-utils/index.d.ts +24 -1
  7. package/package.json +7 -3
  8. package/src/__tests__/publish.test.ts +8 -7
  9. package/src/auth/__tests__/access-key-coverage.test.ts +42 -0
  10. package/src/auth/__tests__/access-key-scan.ts +117 -0
  11. package/src/auth/__tests__/access-keys.test.ts +81 -0
  12. package/src/auth/__tests__/access-model.test.ts +257 -0
  13. package/src/auth/__tests__/access-test-fixtures.ts +50 -0
  14. package/src/auth/__tests__/key-catalog-drift.test.ts +33 -0
  15. package/src/auth/__tests__/platform-admin-bypass-parity.test.ts +67 -0
  16. package/src/auth/access-keys.ts +173 -0
  17. package/src/auth/access-model.ts +185 -0
  18. package/src/auth/index.ts +6 -2
  19. package/src/auth/multi-tenancy/memberships/membership.ts +2 -4
  20. package/src/auth/multi-tenancy/permissions.ts +1 -1
  21. package/src/auth/multi-tenancy/types.ts +3 -12
  22. package/src/business/acquisition/api-schemas.test.ts +59 -8
  23. package/src/business/acquisition/api-schemas.ts +10 -5
  24. package/src/business/acquisition/build-templates.test.ts +187 -240
  25. package/src/business/acquisition/build-templates.ts +87 -98
  26. package/src/business/acquisition/types.ts +390 -389
  27. package/src/execution/engine/index.ts +6 -4
  28. package/src/execution/engine/tools/lead-service-types.ts +63 -34
  29. package/src/execution/engine/tools/platform/acquisition/types.ts +7 -8
  30. package/src/execution/engine/tools/registry.ts +6 -4
  31. package/src/execution/engine/tools/tool-maps.ts +23 -1
  32. package/src/organization-model/domains/prospecting.ts +2 -327
  33. package/src/organization-model/migration-helpers.ts +16 -12
  34. package/src/reference/_generated/contracts.md +352 -328
  35. package/src/reference/glossary.md +8 -6
  36. package/src/supabase/database.types.ts +13 -0
@@ -326,10 +326,12 @@ export {
326
326
  type TransitionDealParams,
327
327
  type LoadDealParams,
328
328
  // List-oriented lead gen params (Step 3 of list-oriented migration)
329
- type UpdateListConfigParams,
330
- type UpdateCompanyStageParams,
331
- type UpdateContactStageParams,
332
- type ListPendingCompanyIdsParams,
329
+ type UpdateListConfigParams,
330
+ type UpdateCompanyStageParams,
331
+ type UpdateContactStageParams,
332
+ type ClearCompanyStagesParams,
333
+ type ClearContactStagesParams,
334
+ type ListPendingCompanyIdsParams,
333
335
  type ListPendingContactIdsParams,
334
336
  type AddCompaniesToListParams,
335
337
  type AddCompaniesToListResult,
@@ -529,23 +529,39 @@ export interface UpdateListConfigParams {
529
529
  pipelineConfig?: PipelineConfig
530
530
  }
531
531
 
532
- export interface UpdateCompanyStageParams {
533
- organizationId: string
534
- listId: string
535
- companyId: string
536
- stage: string
537
- status?: ProcessingStageStatus
538
- executionId?: string
539
- }
540
-
541
- export interface UpdateContactStageParams {
542
- organizationId: string
543
- listId: string
544
- contactId: string
545
- stage: string
546
- status?: ProcessingStageStatus
547
- executionId?: string
548
- }
532
+ export interface UpdateCompanyStageParams {
533
+ organizationId: string
534
+ listId: string
535
+ companyId: string
536
+ stage: string
537
+ status?: ProcessingStageStatus
538
+ data?: unknown
539
+ executionId?: string
540
+ }
541
+
542
+ export interface UpdateContactStageParams {
543
+ organizationId: string
544
+ listId: string
545
+ contactId: string
546
+ stage: string
547
+ status?: ProcessingStageStatus
548
+ data?: unknown
549
+ executionId?: string
550
+ }
551
+
552
+ export interface ClearCompanyStagesParams {
553
+ organizationId: string
554
+ listId: string
555
+ companyId: string
556
+ stages: string[]
557
+ }
558
+
559
+ export interface ClearContactStagesParams {
560
+ organizationId: string
561
+ listId: string
562
+ contactId: string
563
+ stages: string[]
564
+ }
549
565
 
550
566
  export interface ListPendingCompanyIdsParams {
551
567
  organizationId: string
@@ -589,14 +605,15 @@ export interface RecordListExecutionParams {
589
605
  configSnapshot?: Record<string, unknown>
590
606
  }
591
607
 
592
- export interface ListExecutionSummary {
593
- executionId: string
594
- resourceId: string
595
- status: string
596
- createdAt: string
597
- completedAt: string | null
598
- durationMs: number | null
599
- }
608
+ export interface ListExecutionSummary {
609
+ executionId: string
610
+ resourceId: string
611
+ status: string
612
+ createdAt: string
613
+ completedAt: string | null
614
+ durationMs: number | null
615
+ input?: unknown
616
+ }
600
617
 
601
618
  // Bulk import (contacts)
602
619
  export interface BulkImportParams {
@@ -712,15 +729,27 @@ export interface ILeadService {
712
729
  */
713
730
  getListProgress(listId: string, organizationId: string): Promise<ListProgress | null>
714
731
 
715
- /**
716
- * Advance a company row within a list's explicit stage journey.
717
- */
718
- updateCompanyStage(params: UpdateCompanyStageParams): Promise<void>
719
-
720
- /**
721
- * Advance a contact row within a list's explicit stage journey.
722
- */
723
- updateContactStage(params: UpdateContactStageParams): Promise<void>
732
+ /**
733
+ * Advance a company row within a list's explicit stage journey.
734
+ */
735
+ updateCompanyStage(params: UpdateCompanyStageParams): Promise<void>
736
+
737
+ /**
738
+ * Advance a contact row within a list's explicit stage journey.
739
+ */
740
+ updateContactStage(params: UpdateContactStageParams): Promise<void>
741
+
742
+ /**
743
+ * Clear company stage entries by deleting keys from processing_state.
744
+ * Missing stage keys represent "not attempted"; never persist null markers.
745
+ */
746
+ clearCompanyStages(params: ClearCompanyStagesParams): Promise<void>
747
+
748
+ /**
749
+ * Clear contact stage entries by deleting keys from processing_state.
750
+ * Missing stage keys represent "not attempted"; never persist null markers.
751
+ */
752
+ clearContactStages(params: ClearContactStagesParams): Promise<void>
724
753
 
725
754
  /**
726
755
  * Return the company_ids in acq_list_companies for the given list where
@@ -1,6 +1,5 @@
1
- import { z } from 'zod'
2
- import { isProspectingBuildTemplateId } from '../../../../../business/acquisition/build-templates'
3
- import {
1
+ import { z } from 'zod'
2
+ import {
4
3
  CompanyProcessingStateSchema,
5
4
  ContactProcessingStateSchema
6
5
  } from '../../../../../business/acquisition/api-schemas'
@@ -10,11 +9,11 @@ import { EmailSchema } from '../../../../../platform/utils/validation'
10
9
  // LIST SCHEMAS
11
10
  // ============================================
12
11
 
13
- export const CreateListInputSchema = z.object({
14
- name: z.string().min(1),
15
- description: z.string().optional(),
16
- buildTemplateId: z.string().refine(isProspectingBuildTemplateId).optional()
17
- })
12
+ export const CreateListInputSchema = z.object({
13
+ name: z.string().min(1),
14
+ description: z.string().optional(),
15
+ buildTemplateId: z.string().trim().min(1).max(100).optional()
16
+ })
18
17
 
19
18
  export const UpdateListInputSchema = z.object({
20
19
  id: z.string().uuid(),
@@ -587,10 +587,12 @@ export {
587
587
  type GetDealByIdParams,
588
588
  type GetContactByIdParams,
589
589
  type GetCompanyByIdParams,
590
- type UpdateListConfigParams,
591
- type UpdateCompanyStageParams,
592
- type UpdateContactStageParams,
593
- type ListPendingCompanyIdsParams,
590
+ type UpdateListConfigParams,
591
+ type UpdateCompanyStageParams,
592
+ type UpdateContactStageParams,
593
+ type ClearCompanyStagesParams,
594
+ type ClearContactStagesParams,
595
+ type ListPendingCompanyIdsParams,
594
596
  type ListPendingContactIdsParams,
595
597
  type AddCompaniesToListParams,
596
598
  type AddCompaniesToListResult,
@@ -258,6 +258,8 @@ import type {
258
258
  RecordListExecutionParams,
259
259
  UpdateCompanyStageParams,
260
260
  UpdateContactStageParams,
261
+ ClearCompanyStagesParams,
262
+ ClearContactStagesParams,
261
263
  ListPendingCompanyIdsParams,
262
264
  ListPendingContactIdsParams,
263
265
  UpsertSocialPostParams,
@@ -589,6 +591,14 @@ export type LeadToolMap = {
589
591
  params: Omit<UpdateContactStageParams, 'organizationId'>
590
592
  result: void
591
593
  }
594
+ clearCompanyStages: {
595
+ params: Omit<ClearCompanyStagesParams, 'organizationId'>
596
+ result: void
597
+ }
598
+ clearContactStages: {
599
+ params: Omit<ClearContactStagesParams, 'organizationId'>
600
+ result: void
601
+ }
592
602
  // Company operations
593
603
  createCompany: { params: Omit<CreateCompanyParams, 'organizationId'>; result: AcqCompany }
594
604
  upsertCompany: { params: Omit<UpsertCompanyParams, 'organizationId'>; result: AcqCompany }
@@ -713,7 +723,11 @@ export type LeadToolMap = {
713
723
  export type ListToolMap = {
714
724
  getConfig: {
715
725
  params: { listId: string }
716
- result: { scrapingConfig: ScrapingConfig; icp: IcpRubric; pipelineConfig: PipelineConfig }
726
+ result: {
727
+ scrapingConfig: ScrapingConfig
728
+ icp: IcpRubric
729
+ pipelineConfig: PipelineConfig & { dataMode: 'mock' | 'live' }
730
+ }
717
731
  }
718
732
  recordExecution: {
719
733
  params: Omit<RecordListExecutionParams, 'organizationId'>
@@ -727,6 +741,14 @@ export type ListToolMap = {
727
741
  params: Omit<UpdateContactStageParams, 'organizationId'>
728
742
  result: void
729
743
  }
744
+ clearCompanyStages: {
745
+ params: Omit<ClearCompanyStagesParams, 'organizationId'>
746
+ result: void
747
+ }
748
+ clearContactStages: {
749
+ params: Omit<ClearContactStagesParams, 'organizationId'>
750
+ result: void
751
+ }
730
752
  listPendingCompanyIds: {
731
753
  params: Omit<ListPendingCompanyIdsParams, 'organizationId'>
732
754
  result: string[]
@@ -62,344 +62,19 @@ export const ProspectingBuildTemplateSchema = DisplayMetadataSchema.extend({
62
62
  })
63
63
 
64
64
  export type ListBuilderStep = z.infer<typeof ProspectingBuildTemplateStepSchema>
65
+ export type ProspectingBuildTemplate = z.infer<typeof ProspectingBuildTemplateSchema>
65
66
  export type RecordColumnConfig = z.infer<typeof RecordColumnConfigSchema>
66
67
  export type CredentialRequirement = z.infer<typeof CredentialRequirementSchema>
67
- /**
68
- * Generic template name type. Elevasis-specific template names
69
- * ('localServices' | 'dtcApolloClickup') have been relocated to
70
- * `@repo/elevasis-core/src/organization-model/actions.ts`.
71
- */
72
68
  export type TemplateName = string
73
69
  export type StepName = string
74
-
75
- const DTC_RECORD_COLUMNS = {
76
- populated: {
77
- company: [
78
- { key: 'name', label: 'Company', path: 'company.name' },
79
- { key: 'domain', label: 'Domain', path: 'company.domain' },
80
- { key: 'employee-count', label: 'Employees', path: 'company.numEmployees', renderType: 'count' },
81
- { key: 'apollo-industry', label: 'Apollo industry', path: 'company.category' },
82
- { key: 'location', label: 'Location', path: 'company.locationState' }
83
- ]
84
- },
85
- crawled: {
86
- company: [
87
- { key: 'name', label: 'Company', path: 'company.name' },
88
- { key: 'domain', label: 'Domain', path: 'company.domain' },
89
- { key: 'page-count', label: 'Pages', path: 'company.enrichmentData.websiteCrawl.pageCount', renderType: 'count' },
90
- { key: 'crawl-status', label: 'Crawl status', path: 'processingState.crawled.status', renderType: 'badge' }
91
- ]
92
- },
93
- extracted: {
94
- company: [
95
- { key: 'name', label: 'Company', path: 'company.name' },
96
- { key: 'domain', label: 'Domain', path: 'company.domain' },
97
- { key: 'description', label: 'Description', path: 'company.enrichmentData.websiteCrawl.companyDescription' },
98
- { key: 'services', label: 'Services', path: 'company.enrichmentData.websiteCrawl.services', renderType: 'json' },
99
- {
100
- key: 'automation-gaps',
101
- label: 'Automation gaps',
102
- path: 'company.enrichmentData.websiteCrawl.automationGaps',
103
- renderType: 'json'
104
- },
105
- {
106
- key: 'contact-count',
107
- label: 'Contacts',
108
- path: 'company.enrichmentData.websiteCrawl.emailCount',
109
- renderType: 'count'
110
- }
111
- ]
112
- },
113
- qualified: {
114
- company: [
115
- { key: 'name', label: 'Company', path: 'company.name' },
116
- { key: 'domain', label: 'Domain', path: 'company.domain' },
117
- { key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
118
- { key: 'signals', label: 'Signals', path: 'company.qualificationSignals', renderType: 'json' },
119
- {
120
- key: 'disqualified-reason',
121
- label: 'Disqualified reason',
122
- path: 'processingState.qualified.data.disqualifiedReason'
123
- }
124
- ]
125
- },
126
- decisionMakers: {
127
- contact: [
128
- { key: 'name', label: 'Name', path: 'contact.name' },
129
- { key: 'title', label: 'Title', path: 'contact.title' },
130
- { key: 'email', label: 'Email', path: 'contact.email' },
131
- { key: 'linkedin', label: 'LinkedIn', path: 'contact.linkedinUrl' },
132
- {
133
- key: 'priority-score',
134
- label: 'Priority',
135
- path: 'contact.enrichmentData.apollo.priorityScore',
136
- renderType: 'badge'
137
- }
138
- ]
139
- },
140
- uploaded: {
141
- company: [
142
- { key: 'name', label: 'Company', path: 'company.name' },
143
- { key: 'domain', label: 'Domain', path: 'company.domain' },
144
- {
145
- key: 'contacts',
146
- label: 'Contacts',
147
- path: 'company.enrichmentData.approvedLeadListExport.contacts',
148
- renderType: 'json'
149
- },
150
- { key: 'score', label: 'Score', path: 'company.qualificationScore', renderType: 'badge', badgeColor: 'green' },
151
- {
152
- key: 'approval',
153
- label: 'Approval',
154
- path: 'company.enrichmentData.approvedLeadListExport.approvalStatus',
155
- renderType: 'badge'
156
- }
157
- ]
158
- }
159
- } as const satisfies Record<string, z.infer<typeof RecordColumnsConfigSchema>>
160
-
161
70
  export type ActionRegistry = TopLevelAction[]
162
71
 
163
72
  /**
164
73
  * Generic empty default for the action registry.
165
- * The Elevasis-specific lead-gen action list has been relocated to
166
- * `@repo/elevasis-core/src/organization-model/actions.ts`.
74
+ * Tenant-specific lead-gen action lists belong in tenant Organization Models.
167
75
  */
168
76
  export const ACTION_REGISTRY: ActionRegistry = []
169
77
 
170
78
  export function findActionById(id: string): TopLevelAction | undefined {
171
79
  return findOrganizationActionById(id)
172
80
  }
173
-
174
- export const PROSPECTING_STEPS = {
175
- localServices: {
176
- sourceCompanies: {
177
- id: 'source-companies',
178
- label: 'Companies found',
179
- primaryEntity: 'company',
180
- outputs: ['company'],
181
- stageKey: 'populated',
182
- dependencyMode: 'per-record-eligibility',
183
- actionKey: 'lead-gen.company.source',
184
- defaultBatchSize: 100,
185
- maxBatchSize: 250
186
- },
187
- analyzeWebsites: {
188
- id: 'analyze-websites',
189
- label: 'Websites analyzed',
190
- primaryEntity: 'company',
191
- outputs: ['company'],
192
- stageKey: 'extracted',
193
- dependsOn: ['source-companies'],
194
- dependencyMode: 'per-record-eligibility',
195
- actionKey: 'lead-gen.company.website-extract',
196
- defaultBatchSize: 50,
197
- maxBatchSize: 100
198
- },
199
- qualifyCompanies: {
200
- id: 'qualify-companies',
201
- label: 'Companies qualified',
202
- primaryEntity: 'company',
203
- outputs: ['company'],
204
- stageKey: 'qualified',
205
- dependsOn: ['analyze-websites'],
206
- dependencyMode: 'per-record-eligibility',
207
- actionKey: 'lead-gen.company.qualify',
208
- defaultBatchSize: 100,
209
- maxBatchSize: 250
210
- },
211
- findContacts: {
212
- id: 'find-contacts',
213
- label: 'Decision-makers found',
214
- primaryEntity: 'contact',
215
- outputs: ['contact'],
216
- stageKey: 'discovered',
217
- dependsOn: ['qualify-companies'],
218
- dependencyMode: 'per-record-eligibility',
219
- actionKey: 'lead-gen.contact.discover',
220
- defaultBatchSize: 50,
221
- maxBatchSize: 100
222
- },
223
- verifyEmails: {
224
- id: 'verify-emails',
225
- label: 'Emails verified',
226
- primaryEntity: 'contact',
227
- outputs: ['contact'],
228
- stageKey: 'verified',
229
- dependsOn: ['find-contacts'],
230
- dependencyMode: 'per-record-eligibility',
231
- actionKey: 'lead-gen.contact.verify-email',
232
- defaultBatchSize: 100,
233
- maxBatchSize: 500
234
- },
235
- personalize: {
236
- id: 'personalize',
237
- label: 'Personalize',
238
- primaryEntity: 'contact',
239
- outputs: ['contact'],
240
- stageKey: 'personalized',
241
- dependsOn: ['verify-emails'],
242
- dependencyMode: 'per-record-eligibility',
243
- actionKey: 'lead-gen.contact.personalize',
244
- defaultBatchSize: 25,
245
- maxBatchSize: 100
246
- },
247
- review: {
248
- id: 'review',
249
- label: 'Reviewed and exported',
250
- primaryEntity: 'contact',
251
- outputs: ['export'],
252
- stageKey: 'uploaded',
253
- dependsOn: ['personalize'],
254
- dependencyMode: 'per-record-eligibility',
255
- actionKey: 'lead-gen.review.outreach-ready',
256
- defaultBatchSize: 25,
257
- maxBatchSize: 100
258
- }
259
- },
260
- dtcApolloClickup: {
261
- importApolloSearch: {
262
- id: 'import-apollo-search',
263
- label: 'Companies found',
264
- description: 'Pull companies and seed contact data from a predefined Apollo search or list.',
265
- primaryEntity: 'company',
266
- outputs: ['company', 'contact'],
267
- stageKey: 'populated',
268
- dependencyMode: 'per-record-eligibility',
269
- actionKey: 'lead-gen.company.apollo-import',
270
- defaultBatchSize: 250,
271
- maxBatchSize: 1000,
272
- recordColumns: DTC_RECORD_COLUMNS.populated,
273
- credentialRequirements: [
274
- {
275
- key: 'apollo',
276
- provider: 'apollo',
277
- credentialType: 'api-key-secret',
278
- label: 'Apollo API key',
279
- required: true,
280
- selectionMode: 'single',
281
- inputPath: 'credential'
282
- }
283
- ]
284
- },
285
- apifyCrawl: {
286
- id: 'apify-crawl',
287
- label: 'Websites crawled',
288
- description:
289
- '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.',
290
- primaryEntity: 'company',
291
- outputs: ['company'],
292
- stageKey: 'crawled',
293
- dependsOn: ['import-apollo-search'],
294
- dependencyMode: 'per-record-eligibility',
295
- actionKey: 'lead-gen.company.apify-crawl',
296
- defaultBatchSize: 50,
297
- maxBatchSize: 100,
298
- recordColumns: DTC_RECORD_COLUMNS.crawled,
299
- credentialRequirements: [
300
- {
301
- key: 'apify',
302
- provider: 'apify',
303
- credentialType: 'api-key-secret',
304
- label: 'Apify API token',
305
- required: true,
306
- selectionMode: 'single',
307
- inputPath: 'credential',
308
- verifyOnRun: true
309
- }
310
- ]
311
- },
312
- analyzeWebsites: {
313
- id: 'analyze-websites',
314
- label: 'Websites analyzed',
315
- description: 'Extract subscription, product, retention, and tech-stack signals from each brand website.',
316
- primaryEntity: 'company',
317
- outputs: ['company'],
318
- stageKey: 'extracted',
319
- dependsOn: ['apify-crawl'],
320
- dependencyMode: 'per-record-eligibility',
321
- actionKey: 'lead-gen.company.website-extract',
322
- defaultBatchSize: 50,
323
- maxBatchSize: 100,
324
- recordColumns: DTC_RECORD_COLUMNS.extracted
325
- },
326
- scoreDtcFit: {
327
- id: 'score-dtc-fit',
328
- label: 'Companies qualified',
329
- description: 'Classify subscription potential, consumable-product fit, retention maturity, and disqualifiers.',
330
- primaryEntity: 'company',
331
- outputs: ['company'],
332
- stageKey: 'qualified',
333
- dependsOn: ['analyze-websites'],
334
- dependencyMode: 'per-record-eligibility',
335
- actionKey: 'lead-gen.company.dtc-subscription-qualify',
336
- defaultBatchSize: 100,
337
- maxBatchSize: 250,
338
- recordColumns: DTC_RECORD_COLUMNS.qualified
339
- },
340
- enrichDecisionMakers: {
341
- id: 'enrich-decision-makers',
342
- label: 'Decision-makers found',
343
- description:
344
- 'Use Apollo to find qualified contacts at qualified companies - founders, retention leads, lifecycle leads, and marketing owners.',
345
- primaryEntity: 'company',
346
- outputs: ['contact'],
347
- stageKey: 'decision-makers-enriched',
348
- recordEntity: 'contact',
349
- dependsOn: ['score-dtc-fit'],
350
- dependencyMode: 'per-record-eligibility',
351
- actionKey: 'lead-gen.contact.apollo-decision-maker-enrich',
352
- defaultBatchSize: 100,
353
- maxBatchSize: 250,
354
- recordColumns: DTC_RECORD_COLUMNS.decisionMakers,
355
- credentialRequirements: [
356
- {
357
- key: 'apollo',
358
- provider: 'apollo',
359
- credentialType: 'api-key-secret',
360
- label: 'Apollo API key',
361
- required: true,
362
- selectionMode: 'single',
363
- inputPath: 'credential'
364
- }
365
- ]
366
- },
367
- reviewAndExport: {
368
- id: 'review-and-export',
369
- label: 'Reviewed and exported',
370
- description:
371
- 'Operator QC approves or rejects qualified companies, then approved records are exported as a lead list with unverified emails.',
372
- primaryEntity: 'company',
373
- outputs: ['export'],
374
- stageKey: 'uploaded',
375
- recordsStageKey: 'uploaded',
376
- recordSourceStageKey: 'qualified',
377
- dependsOn: ['enrich-decision-makers'],
378
- dependencyMode: 'per-record-eligibility',
379
- actionKey: 'lead-gen.export.list',
380
- defaultBatchSize: 100,
381
- maxBatchSize: 250,
382
- recordColumns: DTC_RECORD_COLUMNS.uploaded,
383
- credentialRequirements: [
384
- {
385
- key: 'clickup',
386
- provider: 'clickup',
387
- credentialType: 'api-key-secret',
388
- label: 'ClickUp API token',
389
- required: true,
390
- selectionMode: 'single',
391
- inputPath: 'clickupCredential',
392
- verifyOnRun: true
393
- }
394
- ]
395
- }
396
- }
397
- } as const satisfies Record<TemplateName, Record<StepName, ListBuilderStep>>
398
-
399
- // OrganizationModelProspectingSchema and DEFAULT_ORGANIZATION_MODEL_PROSPECTING are retired.
400
- // Build-template/stage data lives in System.ontology.catalogTypes. Use
401
- // getAllBuildTemplates() / getAllProspectingStages() from migration-helpers to
402
- // read prospecting data portably.
403
- //
404
- // ProspectingBuildTemplateSchema, ProspectingBuildTemplateStepSchema, ProspectingLifecycleStageSchema,
405
- // PROSPECTING_STEPS, and ACTION_REGISTRY are retained for use by business logic and UI.
@@ -127,20 +127,24 @@ export function getAllBuildTemplates(model: OrganizationModel): BuildTemplate[]
127
127
  const steps = stepCatalog === undefined ? [] : entriesOf(stepCatalog)
128
128
 
129
129
  return {
130
- id: templateId,
131
- label: stringValue(templateEntry.label) ?? templateId,
132
- ...(stringValue(templateEntry.description) ? { description: stringValue(templateEntry.description) } : {}),
133
- ...(stringValue(templateEntry.color) ? { color: stringValue(templateEntry.color) } : {}),
134
- steps: steps.map(([stepId, step]) => ({
135
- id: stepId,
136
- label: stringValue(step.label) ?? stepId,
137
- ...(stringValue(step.description) ? { description: stringValue(step.description) } : {}),
138
- ...step
139
- })) as BuildTemplate['steps']
140
- } satisfies BuildTemplate
130
+ order: numberValue(templateEntry.order, Number.MAX_SAFE_INTEGER),
131
+ template: {
132
+ id: templateId,
133
+ label: stringValue(templateEntry.label) ?? templateId,
134
+ ...(stringValue(templateEntry.description) ? { description: stringValue(templateEntry.description) } : {}),
135
+ ...(stringValue(templateEntry.color) ? { color: stringValue(templateEntry.color) } : {}),
136
+ steps: steps.map(([stepId, step]) => ({
137
+ id: stepId,
138
+ label: stringValue(step.label) ?? stepId,
139
+ ...(stringValue(step.description) ? { description: stringValue(step.description) } : {}),
140
+ ...step
141
+ })) as BuildTemplate['steps']
142
+ } satisfies BuildTemplate
143
+ }
141
144
  })
142
145
  )
143
- .sort((a, b) => a.id.localeCompare(b.id))
146
+ .sort((a, b) => a.order - b.order || a.template.id.localeCompare(b.template.id))
147
+ .map(({ template }) => template)
144
148
  }
145
149
 
146
150
  export function getAllProspectingStages(model: OrganizationModel, kind: 'company' | 'contact'): ProspectingStage[] {