@elevasis/core 0.13.0 → 0.15.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 (42) 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 +2336 -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 +317 -73
  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 +101 -78
  16. package/src/business/acquisition/index.ts +51 -9
  17. package/src/business/acquisition/stateful.ts +30 -0
  18. package/src/business/acquisition/types.ts +48 -80
  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 +934 -874
  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 +30 -2
  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.test.ts +189 -0
  37. package/src/organization-model/domains/sales.ts +456 -94
  38. package/src/organization-model/published.ts +21 -21
  39. package/src/organization-model/resolve.ts +21 -8
  40. package/src/platform/constants/versions.ts +1 -1
  41. package/src/reference/_generated/contracts.md +2336 -0
  42. package/src/supabase/database.types.ts +2958 -2886
@@ -1,90 +1,113 @@
1
+ import { z } from 'zod'
2
+ import {
3
+ CRM_DISCOVERY_BOOKING_CANCELLED_STATE,
4
+ CRM_DISCOVERY_LINK_SENT_STATE,
5
+ CRM_DISCOVERY_NUDGING_STATE,
6
+ CRM_DISCOVERY_REPLIED_STATE
7
+ } from '@repo/core/organization-model'
1
8
  import type { AcqDealRow } from './types'
2
9
 
3
10
  export interface Action {
4
11
  key: string
5
12
  label: string
6
- kind: 'transition' | 'edit' | 'modal'
7
- payload?: Record<string, unknown>
13
+ payloadSchema?: z.ZodTypeAny
8
14
  }
9
15
 
10
- // Stage order inlined from DEFAULT_ORGANIZATION_MODEL_SALES (single caller).
11
- // Update here if the default pipeline changes.
12
- const STAGE_ORDER = ['interested', 'proposal', 'closing', 'closed_won', 'closed_lost', 'nurturing'] as const
13
-
14
- type DefaultStageKey = (typeof STAGE_ORDER)[number]
15
-
16
- function isDefaultStage(key: string | null | undefined): key is DefaultStageKey {
17
- return STAGE_ORDER.includes(key as DefaultStageKey)
18
- }
19
-
20
- function transitionAction(stageKey: DefaultStageKey): Action {
21
- const labels: Record<DefaultStageKey, string> = {
22
- interested: 'Move to Interested',
23
- proposal: 'Move to Proposal',
24
- closing: 'Move to Closing',
25
- closed_won: 'Close Won',
26
- closed_lost: 'Close Lost',
27
- nurturing: 'Move to Nurturing'
28
- }
29
- return {
30
- key: `move_to_${stageKey}`,
31
- label: labels[stageKey],
32
- kind: 'transition',
33
- payload: { stageKey }
34
- }
35
- }
36
-
37
- function interestedActions(stateKey: string | null | undefined): Action[] {
38
- const base: Action[] = [transitionAction('proposal'), transitionAction('closed_lost'), transitionAction('nurturing')]
39
-
40
- if (stateKey === 'discovery_replied') {
41
- return [...base, { key: 'send_link', label: 'Send Booking Link', kind: 'modal' }]
42
- }
43
-
44
- if (stateKey === 'discovery_link_sent') {
45
- return [...base, { key: 'send_nudge', label: 'Send Nudge', kind: 'modal' }]
46
- }
47
-
48
- if (stateKey === 'discovery_nudging') {
49
- return [
50
- ...base,
51
- { key: 'send_nudge', label: 'Send Nudge', kind: 'modal' },
52
- { key: 'mark_no_show', label: 'Mark No-Show', kind: 'transition' }
53
- ]
54
- }
55
-
56
- if (stateKey === 'discovery_booking_cancelled') {
57
- return [...base, { key: 'rebook', label: 'Rebook', kind: 'modal' }]
58
- }
59
-
60
- return base
61
- }
62
-
63
- function proposalActions(): Action[] {
64
- return [transitionAction('closing'), transitionAction('closed_lost'), transitionAction('nurturing')]
65
- }
66
-
67
- function closingActions(): Action[] {
68
- return [transitionAction('closed_won'), transitionAction('closed_lost'), transitionAction('nurturing')]
16
+ export interface ActionDef {
17
+ key: string
18
+ label: string
19
+ isAvailableFor: (deal: AcqDealRow) => boolean
20
+ workflowId: string
21
+ payloadSchema?: z.ZodTypeAny
69
22
  }
70
23
 
71
- export function deriveActions(deal: AcqDealRow): Action[] {
72
- const stage = deal.stage_key
73
-
74
- if (!isDefaultStage(stage)) {
75
- return []
24
+ export const SendReplyActionPayloadSchema = z
25
+ .object({
26
+ replyBody: z.string().trim().min(1).max(10000)
27
+ })
28
+ .strict()
29
+
30
+ export const DEFAULT_CRM_ACTIONS: ActionDef[] = [
31
+ {
32
+ key: 'move_to_proposal',
33
+ label: 'Move to Proposal',
34
+ isAvailableFor: (deal) => deal.stage_key === 'interested',
35
+ workflowId: 'move_to_proposal-workflow'
36
+ },
37
+ {
38
+ key: 'move_to_closing',
39
+ label: 'Move to Closing',
40
+ isAvailableFor: (deal) => deal.stage_key === 'proposal',
41
+ workflowId: 'move_to_closing-workflow'
42
+ },
43
+ {
44
+ key: 'move_to_closed_won',
45
+ label: 'Close Won',
46
+ isAvailableFor: (deal) => deal.stage_key === 'closing',
47
+ workflowId: 'move_to_closed_won-workflow'
48
+ },
49
+ {
50
+ key: 'move_to_closed_lost',
51
+ label: 'Close Lost',
52
+ isAvailableFor: (deal) =>
53
+ deal.stage_key === 'interested' || deal.stage_key === 'proposal' || deal.stage_key === 'closing',
54
+ workflowId: 'move_to_closed_lost-workflow'
55
+ },
56
+ {
57
+ key: 'move_to_nurturing',
58
+ label: 'Move to Nurturing',
59
+ isAvailableFor: (deal) =>
60
+ deal.stage_key === 'interested' || deal.stage_key === 'proposal' || deal.stage_key === 'closing',
61
+ workflowId: 'move_to_nurturing-workflow'
62
+ },
63
+ {
64
+ key: 'send_reply',
65
+ label: 'Send Reply',
66
+ isAvailableFor: (deal) =>
67
+ deal.stage_key === 'interested' &&
68
+ (deal.state_key === CRM_DISCOVERY_REPLIED_STATE.stateKey ||
69
+ deal.state_key === CRM_DISCOVERY_LINK_SENT_STATE.stateKey ||
70
+ deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey),
71
+ workflowId: 'crm-send-reply-workflow',
72
+ payloadSchema: SendReplyActionPayloadSchema
73
+ },
74
+ {
75
+ key: 'send_link',
76
+ label: 'Send Booking Link',
77
+ isAvailableFor: (deal) =>
78
+ deal.stage_key === 'interested' && deal.state_key === CRM_DISCOVERY_REPLIED_STATE.stateKey,
79
+ workflowId: 'crm-send-booking-link-workflow'
80
+ },
81
+ {
82
+ key: 'send_nudge',
83
+ label: 'Send Nudge',
84
+ isAvailableFor: (deal) =>
85
+ deal.stage_key === 'interested' &&
86
+ (deal.state_key === CRM_DISCOVERY_LINK_SENT_STATE.stateKey ||
87
+ deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey),
88
+ workflowId: 'crm-send-nudge-workflow'
89
+ },
90
+ {
91
+ key: 'mark_no_show',
92
+ label: 'Mark No-Show',
93
+ isAvailableFor: (deal) =>
94
+ deal.stage_key === 'interested' && deal.state_key === CRM_DISCOVERY_NUDGING_STATE.stateKey,
95
+ // Mirrors the auto-timeout precedent in operations/sales/crm/pipeline/timeout-actions.ts:
96
+ // both manual-click and timeout move the deal to closed_lost. The action_taken activity
97
+ // event captures operator intent and distinguishes the manual variant from the timed one.
98
+ workflowId: 'mark_no_show-workflow'
99
+ },
100
+ {
101
+ key: 'rebook',
102
+ label: 'Rebook',
103
+ isAvailableFor: (deal) =>
104
+ deal.stage_key === 'interested' && deal.state_key === CRM_DISCOVERY_BOOKING_CANCELLED_STATE.stateKey,
105
+ workflowId: 'crm-rebook-workflow'
76
106
  }
107
+ ]
77
108
 
78
- switch (stage) {
79
- case 'interested':
80
- return interestedActions(deal.state_key)
81
- case 'proposal':
82
- return proposalActions()
83
- case 'closing':
84
- return closingActions()
85
- case 'closed_won':
86
- case 'closed_lost':
87
- case 'nurturing':
88
- return []
89
- }
109
+ export function deriveActions(deal: AcqDealRow, actions: ActionDef[] = DEFAULT_CRM_ACTIONS): Action[] {
110
+ return actions
111
+ .filter((a) => a.isAvailableFor(deal))
112
+ .map(({ key, label, payloadSchema }) => ({ key, label, payloadSchema }))
90
113
  }
@@ -1,6 +1,7 @@
1
1
  export * from './types'
2
2
  export * from './activity-events'
3
3
  export * from './derive-actions'
4
+ export * from './stateful'
4
5
  // Export api-schemas selectively to avoid re-exporting names already defined in types.ts
5
6
  // (DealStage, AcqDealTaskKind are declared as union types there; api-schemas re-infers them from Zod)
6
7
  export {
@@ -30,6 +31,9 @@ export {
30
31
  CreateDealNoteRequestSchema,
31
32
  CreateDealTaskRequestSchema,
32
33
  TransitionItemRequestSchema,
34
+ TransitionDealStateRequestSchema,
35
+ ExecuteActionParamsSchema,
36
+ ExecuteActionRequestSchema,
33
37
  DealContactSummarySchema,
34
38
  DealListItemSchema,
35
39
  DealListResponseSchema,
@@ -39,17 +43,18 @@ export {
39
43
  DealTaskResponseSchema,
40
44
  DealTaskListResponseSchema,
41
45
  DealSchemas,
42
- ListQualificationSchema,
43
- ListEnrichmentSchema,
44
- ListPersonalizationSchema,
45
- PipelineStepSchema,
46
- ListPipelineSchema,
47
- ListConfigSchema,
46
+ ListStatusSchema,
47
+ ScrapingConfigSchema,
48
+ IcpRubricSchema,
49
+ PipelineStageSchema,
50
+ PipelineConfigSchema,
51
+ ProcessingStageStatusSchema,
48
52
  ListStageCountsSchema,
49
53
  ListTelemetrySchema,
50
54
  ListIdParamsSchema,
51
55
  CreateListRequestSchema,
52
56
  UpdateListRequestSchema,
57
+ UpdateListStatusRequestSchema,
53
58
  UpdateListConfigRequestSchema,
54
59
  AddCompaniesToListRequestSchema,
55
60
  RemoveCompaniesFromListRequestSchema,
@@ -61,7 +66,34 @@ export {
61
66
  ListTelemetryListResponseSchema,
62
67
  ListExecutionSummarySchema,
63
68
  ListExecutionsResponseSchema,
69
+ ListStageProgressSchema,
70
+ ListProgressResponseSchema,
64
71
  AcqListSchemas,
72
+ AcqSubstrateSchemas,
73
+ AcqArtifactOwnerKindSchema,
74
+ ListArtifactsQuerySchema,
75
+ CreateArtifactRequestSchema,
76
+ AcqArtifactResponseSchema,
77
+ AcqArtifactListResponseSchema,
78
+ ListMembersQuerySchema,
79
+ MemberIdParamsSchema,
80
+ AcqListMemberContactSummarySchema,
81
+ AcqListMemberResponseSchema,
82
+ AcqListMembersResponseSchema,
83
+ ListCompanyIdParamsSchema,
84
+ AcqListCompanyResponseSchema,
85
+ type AcqArtifactOwnerKind,
86
+ type ListArtifactsQuery,
87
+ type CreateArtifactRequest,
88
+ type AcqArtifactResponse,
89
+ type AcqArtifactListResponse,
90
+ type ListMembersQuery,
91
+ type MemberIdParams,
92
+ type AcqListMemberContactSummary,
93
+ type AcqListMemberResponse,
94
+ type AcqListMembersResponse,
95
+ type ListCompanyIdParams,
96
+ type AcqListCompanyResponse,
65
97
  type CompanyIdParams,
66
98
  type ContactIdParams,
67
99
  type ListCompaniesQuery,
@@ -84,19 +116,27 @@ export {
84
116
  type CreateDealNoteRequest,
85
117
  type CreateDealTaskRequest,
86
118
  type TransitionItemRequest,
119
+ type TransitionDealStateRequest,
120
+ type ExecuteActionParams,
121
+ type ExecuteActionRequest,
87
122
  type DealListResponse,
88
123
  type DealDetailResponse,
89
124
  type DealNoteResponse,
90
125
  type DealNoteListResponse,
91
126
  type DealTaskResponse,
92
127
  type DealTaskListResponse,
93
- type ListConfigInput,
128
+ type ListStatus,
129
+ type ScrapingConfig,
130
+ type IcpRubric,
131
+ type PipelineStage,
132
+ type PipelineConfig,
133
+ type ProcessingStageStatus,
94
134
  type ListStageCountsInput,
95
135
  type ListTelemetryInput,
96
- type PipelineStepInput,
97
136
  type ListIdParams,
98
137
  type CreateListRequest,
99
138
  type UpdateListRequest,
139
+ type UpdateListStatusRequest,
100
140
  type UpdateListConfigRequest,
101
141
  type AddCompaniesToListRequest,
102
142
  type RemoveCompaniesFromListRequest,
@@ -107,5 +147,7 @@ export {
107
147
  type ListTelemetryResponse,
108
148
  type ListTelemetryListResponse,
109
149
  type ListExecutionSummaryInput,
110
- type ListExecutionsResponse
150
+ type ListExecutionsResponse,
151
+ type ListStageProgress,
152
+ type ListProgress
111
153
  } from './api-schemas'
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod'
2
+ import { ActivityEventSchema, type ActivityEvent } from './activity-events'
3
+
4
+ /**
5
+ * Stateful trait — the (pipeline_key, stage_key, state_key, activity_log) quartet
6
+ * applied to acq_deals (CRM HITL, shipped 2026-04-27) and being generalized to
7
+ * acq_lists / acq_list_members / acq_list_companies via Track B.
8
+ */
9
+ export interface Stateful {
10
+ pipeline_key: string
11
+ stage_key: string
12
+ state_key: string
13
+ activity_log: ActivityEvent[]
14
+ }
15
+
16
+ export const StatefulSchema = z.object({
17
+ pipeline_key: z.string(),
18
+ stage_key: z.string(),
19
+ state_key: z.string(),
20
+ activity_log: z.array(ActivityEventSchema)
21
+ })
22
+
23
+ /** Generic transition shape — concrete per-entity transitionItem implementations satisfy this. */
24
+ export type TransitionItem<T extends Stateful, TEvent extends ActivityEvent> = (
25
+ item: T,
26
+ transition: { stage_key?: string; state_key?: string; event: TEvent }
27
+ ) => T
28
+
29
+ /** Generic action-derivation shape — concrete per-entity deriveActions implementations satisfy this. */
30
+ export type DeriveActions<T extends Stateful, TAction> = (item: T) => TAction[]
@@ -153,24 +153,54 @@ export interface ContactEnrichmentData {
153
153
  // Domain Types (camelCase transformations of database rows)
154
154
  // =============================================================================
155
155
 
156
- /**
157
- * Acquisition list for organizing contacts and companies.
158
- * Transformed from AcqListRow with camelCase properties.
159
- */
156
+ export type ListStatus = 'draft' | 'enriching' | 'launched' | 'closing' | 'archived'
157
+
158
+ export interface ScrapingConfig {
159
+ source?: string
160
+ query?: string
161
+ filters?: Record<string, unknown>
162
+ [key: string]: unknown
163
+ }
164
+
165
+ export interface IcpRubric {
166
+ targetDescription?: string
167
+ minReviewCount?: number
168
+ minRating?: number
169
+ excludeFranchises?: boolean
170
+ customRules?: string
171
+ qualificationRubricKey?: string | null
172
+ [key: string]: unknown
173
+ }
174
+
175
+ export interface PipelineStage {
176
+ key: string
177
+ label?: string
178
+ description?: string
179
+ resourceId?: string
180
+ inputTemplate?: Record<string, unknown>
181
+ enabled?: boolean
182
+ order?: number
183
+ }
184
+
185
+ export interface PipelineConfig {
186
+ stages: PipelineStage[]
187
+ }
188
+
160
189
  export interface AcqList {
161
190
  id: string
162
191
  organizationId: string
163
192
  name: string
164
193
  description: string | null
165
- type: string
166
194
  batchIds: string[]
167
195
  instantlyCampaignId: string | null
168
- status: string
196
+ status: ListStatus
197
+ scrapingConfig: ScrapingConfig
198
+ icp: IcpRubric
199
+ pipelineConfig: PipelineConfig
169
200
  metadata: Record<string, unknown>
170
201
  launchedAt: Date | null
171
202
  completedAt: Date | null
172
203
  createdAt: Date
173
- config: ListConfig
174
204
  }
175
205
 
176
206
  /**
@@ -198,6 +228,12 @@ export interface AcqCompany {
198
228
  batchId: string | null
199
229
  status: 'active' | 'invalid'
200
230
  verticalResearch: string | null
231
+ /** Track A: flat qualification score (null until a scoring rubric is defined). Added by W1 migration. */
232
+ qualificationScore: number | null
233
+ /** Track A: flat qualification signals jsonb preserving the result payload shape. Added by W1 migration. */
234
+ qualificationSignals: Record<string, unknown> | null
235
+ /** Track A: key identifying the rubric used for qualification. Added by W1 migration. */
236
+ qualificationRubricKey: string | null
201
237
  createdAt: Date
202
238
  updatedAt: Date
203
239
  }
@@ -302,81 +338,13 @@ export interface AcqDealTask {
302
338
  createdByUserId: string | null
303
339
  }
304
340
 
305
- // ─── List Config / Progress / Telemetry Types ────────────────────────────────
306
-
307
- /**
308
- * One ordered step in a list's pipeline. Maps to a deployed workflow
309
- * (e.g. 'lgn-03-company-qualification-workflow'). The `inputTemplate`
310
- * is merged with `{ listId }` at run time to form the workflow input.
311
- */
312
- export interface PipelineStep {
313
- /** Stable key, e.g. 'scrape' | 'extract' | 'qualify' | 'discover' | 'verify' | 'personalize'. */
314
- key: string
315
- /** Human label rendered in the UI stepper. */
316
- label: string
317
- /** Deployed workflow resourceId (e.g. 'lgn-03-company-qualification-workflow'). */
318
- resourceId: string
319
- /** Input defaults merged with `{ listId }` at dispatch. */
320
- inputTemplate: Record<string, unknown>
321
- /** Whether the UI shows the Run button. */
322
- enabled: boolean
323
- /** Display order (ascending). */
324
- order: number
325
- }
326
-
327
- export type CompanyListStage = 'populated' | 'extracted' | 'qualified'
328
-
329
- export type ContactListStage = 'discovered' | 'verified' | 'personalized' | 'uploaded'
341
+ // ─── Progress / Telemetry Types ──────────────────────────────────────────────
330
342
 
331
343
  /**
332
- * Per-list pipeline configuration stored as jsonb in `acq_lists.config`.
333
- *
334
- * `qualification` is the only required subtree. Every other subtree is optional
335
- * and inherits global defaults when omitted: workflows resolve values as
336
- * `list.config.foo ?? globalDefaults.foo`. Seeded rows from
337
- * `20260413000100_backfill_list_configs.sql` only populate `qualification`
338
- * and `scraping`; the rest was intentionally omitted.
339
- */
340
- export interface ListConfig {
341
- qualification: {
342
- /** One-line description of the target vertical/segment. */
343
- targetDescription: string
344
- /** Minimum Google review count to qualify. */
345
- minReviewCount: number
346
- /** Minimum Google star rating to qualify (e.g. 3.0). */
347
- minRating: number
348
- /** Whether to exclude franchises/chains during qualification. */
349
- excludeFranchises: boolean
350
- /** Free-form LLM rules layered on top of the structured criteria. */
351
- customRules: string
352
- }
353
- enrichment?: {
354
- emailDiscovery?: {
355
- primary: 'tomba' | 'anymailfinder'
356
- credentialName?: string
357
- }
358
- emailVerification?: {
359
- provider: 'millionverifier'
360
- threshold?: 'ok' | 'ok+catch_all'
361
- }
362
- }
363
- personalization?: {
364
- industryContext?: string
365
- /** Email body template with tags like {{opening_line}} / {{category_pain}}. */
366
- emailBody?: string
367
- creativeDirection?: string
368
- /** Contradiction-prevention rules layered into the personalization prompt. */
369
- exclusionRules?: string[]
370
- }
371
- pipeline?: {
372
- steps: PipelineStep[]
373
- }
374
- }
375
-
376
- /**
377
- * Live-scan aggregate telemetry for a single list, computed on demand from
378
- * the list junction tables and current contact deliverability state.
379
- */
344
+ * Live-scan aggregate telemetry for a single list, computed on demand from
345
+ * the list junction tables and current contact deliverability state.
346
+ * `stageCounts` are attempted counts from list-row processing_state.
347
+ */
380
348
  export interface ListTelemetry {
381
349
  listId: string
382
350
  totalCompanies: number