@elevasis/core 0.26.0 → 0.28.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 (49) hide show
  1. package/dist/index.d.ts +162 -105
  2. package/dist/index.js +280 -174
  3. package/dist/knowledge/index.d.ts +43 -43
  4. package/dist/organization-model/index.d.ts +162 -105
  5. package/dist/organization-model/index.js +280 -174
  6. package/dist/test-utils/index.d.ts +20 -20
  7. package/dist/test-utils/index.js +184 -126
  8. package/package.json +3 -3
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +976 -1063
  10. package/src/business/acquisition/api-schemas.test.ts +1962 -1841
  11. package/src/business/acquisition/api-schemas.ts +1461 -1464
  12. package/src/business/acquisition/crm-next-action.test.ts +45 -25
  13. package/src/business/acquisition/crm-next-action.ts +227 -220
  14. package/src/business/acquisition/crm-priority.test.ts +41 -8
  15. package/src/business/acquisition/crm-priority.ts +365 -349
  16. package/src/business/acquisition/crm-state-actions.test.ts +208 -153
  17. package/src/business/acquisition/derive-actions.test.ts +90 -13
  18. package/src/business/acquisition/derive-actions.ts +8 -139
  19. package/src/business/acquisition/ontology-validation.ts +72 -158
  20. package/src/business/pdf/sections/investment.ts +1 -1
  21. package/src/business/pdf/sections/summary-investment.ts +1 -1
  22. package/src/execution/engine/tools/tool-maps.ts +872 -831
  23. package/src/organization-model/__tests__/cross-ref.test.ts +167 -0
  24. package/src/organization-model/__tests__/define-domain-record.test.ts +289 -0
  25. package/src/organization-model/__tests__/om-spine-doc-contract.test.ts +56 -0
  26. package/src/organization-model/__tests__/published-zero-leak.test.ts +60 -1
  27. package/src/organization-model/__tests__/resolve.test.ts +1 -1
  28. package/src/organization-model/__tests__/schema-refinements.test.ts +72 -0
  29. package/src/organization-model/cross-ref.ts +175 -0
  30. package/src/organization-model/domains/actions.ts +13 -0
  31. package/src/organization-model/domains/branding.ts +6 -6
  32. package/src/organization-model/domains/customers.ts +95 -78
  33. package/src/organization-model/domains/entities.ts +157 -144
  34. package/src/organization-model/domains/goals.ts +100 -83
  35. package/src/organization-model/domains/knowledge.ts +106 -93
  36. package/src/organization-model/domains/offerings.ts +88 -71
  37. package/src/organization-model/domains/policies.ts +115 -102
  38. package/src/organization-model/domains/roles.ts +109 -96
  39. package/src/organization-model/domains/sales.test.ts +104 -218
  40. package/src/organization-model/domains/sales.ts +212 -375
  41. package/src/organization-model/domains/statuses.ts +351 -339
  42. package/src/organization-model/domains/systems.ts +176 -164
  43. package/src/organization-model/helpers.ts +331 -306
  44. package/src/organization-model/index.ts +43 -0
  45. package/src/organization-model/published.ts +27 -2
  46. package/src/organization-model/schema-refinements.ts +667 -0
  47. package/src/organization-model/schema.ts +8 -715
  48. package/src/platform/constants/versions.ts +1 -1
  49. package/src/reference/_generated/contracts.md +1000 -1087
@@ -2,502 +2,499 @@ import { z } from 'zod'
2
2
  import { UuidSchema, NonEmptyStringSchema } from '../../platform/utils/validation'
3
3
  import { CredentialRequirementSchema, RecordColumnConfigSchema } from '../../organization-model/domains/prospecting'
4
4
  import { isProspectingBuildTemplateId } from './build-templates'
5
- import {
6
- CRM_STAGE_KEYS_FROM_ONTOLOGY,
7
- CRM_STATE_KEYS_FROM_ONTOLOGY
8
- } from './ontology-validation'
9
5
  export { CrmPriorityBucketKeySchema, CrmPriorityBucketOverrideSchema, CrmPriorityOverrideSchema } from './crm-priority'
10
6
  export type { CrmPriorityBucketOverride, CrmPriorityOverride, ResolvedCrmPriorityRuleConfig } from './crm-priority'
11
-
7
+
12
8
  export const ProcessingStageStatusSchema = z.enum(['success', 'no_result', 'skipped', 'error'])
13
9
 
14
10
  export const LeadGenStageKeySchema = z.string().trim().min(1)
15
11
 
16
12
  export const LeadGenActionKeySchema = z.string().trim().min(1)
17
13
 
18
- const crmStageKeys = CRM_STAGE_KEYS_FROM_ONTOLOGY
19
- const crmStateKeys = CRM_STATE_KEYS_FROM_ONTOLOGY
20
-
21
- export const CrmStageKeySchema = z.enum(crmStageKeys)
22
- export const CrmStateKeySchema = z.enum(crmStateKeys)
23
-
24
- export const ProcessingStateEntrySchema = z
25
- .object({
26
- status: ProcessingStageStatusSchema,
27
- data: z.unknown().optional()
28
- })
29
- .passthrough()
30
-
31
- export const ProcessingStateSchema = z.record(LeadGenStageKeySchema, ProcessingStateEntrySchema)
32
- export const CompanyProcessingStateSchema = ProcessingStateSchema
33
- export const ContactProcessingStateSchema = ProcessingStateSchema
34
-
35
- /**
36
- * Deal Management API Schemas
37
- *
38
- * Request/response validation for /api/deals surface.
39
- * Used by both the API route handlers and the frontend hooks.
40
- *
41
- * Table mapping:
42
- * acq_deals -> DealSchemas (list/detail)
43
- * acq_deal_notes -> DealSchemas (note shapes)
44
- * acq_deal_tasks -> DealSchemas (task shapes)
45
- */
46
-
47
- // ---------------------------------------------------------------------------
48
- // Enum literals (must match DB CHECK constraints exactly)
49
- // ---------------------------------------------------------------------------
50
-
51
- export const DealStageSchema = CrmStageKeySchema
52
-
53
- export const AcqDealTaskKindSchema = z.enum(['call', 'email', 'meeting', 'other'])
54
-
55
- // ---------------------------------------------------------------------------
56
- // Params
57
- // ---------------------------------------------------------------------------
58
-
59
- export const DealIdParamsSchema = z.object({
60
- dealId: UuidSchema
61
- })
62
-
63
- export const DealTaskIdParamsSchema = z.object({
64
- dealId: UuidSchema,
65
- taskId: UuidSchema
66
- })
67
-
68
- // ---------------------------------------------------------------------------
69
- // Query schemas (coerce strings from query params)
70
- // ---------------------------------------------------------------------------
71
-
72
- export const ListDealsQuerySchema = z
73
- .object({
74
- stage: DealStageSchema.optional(),
75
- list: UuidSchema.optional(),
76
- batch: z.string().trim().min(1).max(255).optional(),
77
- staleSince: z.string().datetime().optional(),
78
- search: z.string().optional(),
79
- limit: z.coerce.number().int().positive().default(50),
80
- offset: z.coerce.number().int().min(0).default(0)
81
- })
82
- .strict()
83
-
84
- export const DealLookupQuerySchema = z
85
- .object({
86
- search: z.string().trim().min(1).max(200).optional(),
87
- limit: z.coerce.number().int().min(1).max(25).default(10)
88
- })
89
- .strict()
90
-
91
- export const ListDealTasksDueQuerySchema = z
92
- .object({
93
- window: z.enum(['overdue', 'today', 'today_and_overdue', 'upcoming']).optional(),
94
- assigneeUserId: UuidSchema.optional()
95
- })
96
- .strict()
97
-
98
- // ---------------------------------------------------------------------------
99
- // Request body schemas (all use .strict() — rejects unknown fields)
100
- // ---------------------------------------------------------------------------
101
-
102
- export const CreateDealNoteRequestSchema = z
103
- .object({
104
- body: z.string().trim().min(1).max(10000)
105
- })
106
- .strict()
107
-
108
- export const CreateDealTaskRequestSchema = z
109
- .object({
110
- title: z.string().trim().min(1).max(255),
111
- description: z.string().nullable().optional(),
112
- kind: AcqDealTaskKindSchema.optional(),
113
- dueAt: z.string().datetime().nullable().optional(),
114
- assigneeUserId: UuidSchema.nullable().optional()
115
- })
116
- .strict()
117
-
118
- export const TransitionItemRequestSchema = z
119
- .object({
120
- pipelineKey: z.string().min(1),
121
- stageKey: z.string().min(1),
122
- stateKey: z.string().min(1).nullable().optional(),
123
- reason: z.string().optional(),
124
- expectedUpdatedAt: z.string().datetime().optional()
125
- })
126
- .strict()
127
-
14
+ // CRM stage/state catalogs are model-owned (authored in @repo/elevasis-core
15
+ // canonicalOrganizationModel). The published core schema validates only the
16
+ // transport shape; closed-catalog membership is enforced by the caller/API
17
+ // layer via model-injected validators (mirrors LeadGenStageKeySchema).
18
+ export const CrmStageKeySchema = z.string().trim().min(1)
19
+ export const CrmStateKeySchema = z.string().trim().min(1)
20
+
21
+ export const ProcessingStateEntrySchema = z
22
+ .object({
23
+ status: ProcessingStageStatusSchema,
24
+ data: z.unknown().optional()
25
+ })
26
+ .passthrough()
27
+
28
+ export const ProcessingStateSchema = z.record(LeadGenStageKeySchema, ProcessingStateEntrySchema)
29
+ export const CompanyProcessingStateSchema = ProcessingStateSchema
30
+ export const ContactProcessingStateSchema = ProcessingStateSchema
31
+
32
+ /**
33
+ * Deal Management API Schemas
34
+ *
35
+ * Request/response validation for /api/deals surface.
36
+ * Used by both the API route handlers and the frontend hooks.
37
+ *
38
+ * Table mapping:
39
+ * acq_deals -> DealSchemas (list/detail)
40
+ * acq_deal_notes -> DealSchemas (note shapes)
41
+ * acq_deal_tasks -> DealSchemas (task shapes)
42
+ */
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Enum literals (must match DB CHECK constraints exactly)
46
+ // ---------------------------------------------------------------------------
47
+
48
+ export const DealStageSchema = CrmStageKeySchema
49
+
50
+ export const AcqDealTaskKindSchema = z.enum(['call', 'email', 'meeting', 'other'])
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Params
54
+ // ---------------------------------------------------------------------------
55
+
56
+ export const DealIdParamsSchema = z.object({
57
+ dealId: UuidSchema
58
+ })
59
+
60
+ export const DealTaskIdParamsSchema = z.object({
61
+ dealId: UuidSchema,
62
+ taskId: UuidSchema
63
+ })
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Query schemas (coerce strings from query params)
67
+ // ---------------------------------------------------------------------------
68
+
69
+ export const ListDealsQuerySchema = z
70
+ .object({
71
+ stage: DealStageSchema.optional(),
72
+ list: UuidSchema.optional(),
73
+ batch: z.string().trim().min(1).max(255).optional(),
74
+ staleSince: z.string().datetime().optional(),
75
+ search: z.string().optional(),
76
+ limit: z.coerce.number().int().positive().default(50),
77
+ offset: z.coerce.number().int().min(0).default(0)
78
+ })
79
+ .strict()
80
+
81
+ export const DealLookupQuerySchema = z
82
+ .object({
83
+ search: z.string().trim().min(1).max(200).optional(),
84
+ limit: z.coerce.number().int().min(1).max(25).default(10)
85
+ })
86
+ .strict()
87
+
88
+ export const ListDealTasksDueQuerySchema = z
89
+ .object({
90
+ window: z.enum(['overdue', 'today', 'today_and_overdue', 'upcoming']).optional(),
91
+ assigneeUserId: UuidSchema.optional()
92
+ })
93
+ .strict()
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Request body schemas (all use .strict() — rejects unknown fields)
97
+ // ---------------------------------------------------------------------------
98
+
99
+ export const CreateDealNoteRequestSchema = z
100
+ .object({
101
+ body: z.string().trim().min(1).max(10000)
102
+ })
103
+ .strict()
104
+
105
+ export const CreateDealTaskRequestSchema = z
106
+ .object({
107
+ title: z.string().trim().min(1).max(255),
108
+ description: z.string().nullable().optional(),
109
+ kind: AcqDealTaskKindSchema.optional(),
110
+ dueAt: z.string().datetime().nullable().optional(),
111
+ assigneeUserId: UuidSchema.nullable().optional()
112
+ })
113
+ .strict()
114
+
115
+ export const TransitionItemRequestSchema = z
116
+ .object({
117
+ pipelineKey: z.string().min(1),
118
+ stageKey: z.string().min(1),
119
+ stateKey: z.string().min(1).nullable().optional(),
120
+ reason: z.string().optional(),
121
+ expectedUpdatedAt: z.string().datetime().optional()
122
+ })
123
+ .strict()
124
+
128
125
  export const CrmTransitionItemRequestSchema = z
129
126
  .object({
130
127
  pipelineKey: z.string().min(1),
131
- stageKey: CrmStageKeySchema,
132
- stateKey: CrmStateKeySchema.nullable().optional(),
133
- reason: z.string().optional(),
134
- expectedUpdatedAt: z.string().datetime().optional()
135
- })
136
- .strict()
137
-
138
- export const TransitionDealStateRequestSchema = z
139
- .object({
140
- stateKey: CrmStateKeySchema,
141
- reason: z.string().optional(),
142
- expectedUpdatedAt: z.string().datetime().optional()
143
- })
144
- .strict()
145
-
146
- export const ExecuteActionParamsSchema = z
147
- .object({
148
- dealId: UuidSchema,
149
- actionKey: NonEmptyStringSchema
150
- })
151
- .strict()
152
-
153
- export const ExecuteActionRequestSchema = z
154
- .object({
155
- payload: z.record(z.string(), z.unknown()).optional()
156
- })
157
- .strict()
158
-
159
- // ---------------------------------------------------------------------------
160
- // Response schemas (no .strict() — allows forward-compatible additions)
161
- // ---------------------------------------------------------------------------
162
-
163
- /**
164
- * Contact summary nested inside DealListItem / DealDetailResponse.
165
- * Matches the joined shape returned by useDeals / useDealDetail Supabase queries.
166
- */
167
- export const DealContactSummarySchema = z.object({
168
- id: z.string(),
169
- first_name: z.string().nullable(),
170
- last_name: z.string().nullable(),
171
- email: z.string(),
172
- title: z.string().nullable(),
173
- headline: z.string().nullable(),
174
- linkedin_url: z.string().nullable(),
175
- processing_state: ProcessingStateSchema.nullable(),
176
- enrichment_data: z.record(z.string(), z.unknown()).nullable(),
177
- company: z
178
- .object({
179
- id: z.string(),
180
- name: z.string(),
181
- domain: z.string().nullable(),
182
- website: z.string().nullable(),
183
- linkedin_url: z.string().nullable(),
184
- segment: z.string().nullable(),
185
- category: z.string().nullable(),
186
- num_employees: z.number().nullable()
187
- })
188
- .nullable()
189
- })
190
-
191
- export const DealPrioritySchema = z.object({
192
- bucketKey: z.enum(['needs_response', 'follow_up_due', 'waiting', 'stale', 'closed_low']),
193
- rank: z.number().int(),
194
- label: z.string(),
195
- color: z.string(),
196
- reason: z.string(),
197
- latestActivityAt: z.string().nullable(),
198
- nextActionAt: z.string().nullable()
199
- })
200
-
201
- /**
202
- * Deal list item with joined contact (and company via contact).
203
- * Matches DealListItem from @repo/core types.
204
- */
205
- export const DealListItemSchema = z.object({
206
- // acq_deals columns
207
- id: z.string(),
208
- organization_id: z.string(),
209
- client_id: z.string().nullable().optional(),
210
- contact_id: z.string().nullable(),
211
- contact_email: z.string(),
212
- pipeline_key: z.string(),
213
- stage_key: z.string().nullable(),
214
- state_key: z.string().nullable(),
215
- activity_log: z.unknown(),
216
- discovery_data: z.unknown().nullable(),
217
- discovery_submitted_at: z.string().nullable(),
218
- discovery_submitted_by: z.string().nullable(),
219
- proposal_data: z.unknown().nullable(),
220
- proposal_sent_at: z.string().nullable(),
221
- proposal_pdf_url: z.string().nullable(),
222
- signature_envelope_id: z.string().nullable(),
223
- source_list_id: z.string().nullable(),
224
- source_type: z.string().nullable(),
225
- initial_fee: z.number().nullable(),
226
- monthly_fee: z.number().nullable(),
227
- closed_lost_at: z.string().nullable(),
228
- closed_lost_reason: z.string().nullable(),
229
- created_at: z.string(),
230
- updated_at: z.string(),
231
- priority: DealPrioritySchema,
232
- ownership: z.enum(['us', 'them']).nullable(),
233
- nextAction: z.string().nullable(),
234
- // joined relation
235
- contact: DealContactSummarySchema.nullable()
236
- })
237
-
238
- export const DealListResponseSchema = z.object({
239
- data: z.array(DealListItemSchema),
240
- total: z.number().int(),
241
- limit: z.number().int(),
242
- offset: z.number().int()
243
- })
244
-
245
- export const DealStageSummarySchema = z.object({
246
- stage: z.string(),
247
- count: z.number().int(),
248
- totalValue: z.number(),
249
- oldestUpdatedAt: z.string().nullable(),
250
- newestUpdatedAt: z.string().nullable()
251
- })
252
-
253
- export const StaleDealSummarySchema = z.object({
254
- id: z.string(),
255
- contactEmail: z.string(),
256
- stageKey: z.string(),
257
- updatedAt: z.string(),
258
- daysStale: z.number().int()
259
- })
260
-
261
- export const DealSummaryResponseSchema = z.object({
262
- totalDeals: z.number().int(),
263
- openDeals: z.number().int(),
264
- wonDeals: z.number().int(),
265
- lostDeals: z.number().int(),
266
- winRate: z.number(),
267
- avgDealSize: z.number(),
268
- totalPipelineValue: z.number(),
269
- stageSummary: z.array(DealStageSummarySchema),
270
- staleDeals: z.array(StaleDealSummarySchema)
271
- })
272
-
273
- export const DealLookupItemSchema = z.object({
274
- id: z.string(),
275
- contactEmail: z.string(),
276
- stageKey: z.string().nullable(),
277
- updatedAt: z.string(),
278
- contactName: z.string().nullable(),
279
- companyName: z.string().nullable(),
280
- displayLabel: z.string()
281
- })
282
-
283
- export const DealLookupResponseSchema = z.array(DealLookupItemSchema)
284
-
285
- export const ConversationMessageSchema = z.object({
286
- id: z.string(),
287
- direction: z.enum(['inbound', 'outbound']),
288
- fromEmail: z.string(),
289
- toEmail: z.string(),
290
- subject: z.string().nullable(),
291
- body: z.string(),
292
- sentAt: z.string().nullable()
293
- })
294
-
295
- export const DealConversationSchema = z.object({
296
- messages: z.array(ConversationMessageSchema)
297
- })
298
-
299
- export const DealLineageListRefSchema = z.object({
300
- id: z.string(),
301
- name: z.string(),
302
- status: z.string()
303
- })
304
-
305
- export const DealLineageProjectRefSchema = z.object({
306
- id: z.string(),
307
- name: z.string(),
308
- kind: z.string(),
309
- status: z.string(),
310
- updatedAt: z.string()
311
- })
312
-
313
- export const DealLineageClientRefSchema = z.object({
314
- id: z.string(),
315
- name: z.string(),
316
- status: z.string()
317
- })
318
-
319
- export const DealLineageSchema = z.object({
320
- list: DealLineageListRefSchema.nullable(),
321
- projects: z.array(DealLineageProjectRefSchema),
322
- client: DealLineageClientRefSchema.nullable()
323
- })
324
-
325
- /**
326
- * Deal detail shape — currently the same as a list item (full joined record).
327
- * Additive fields keep existing DealListItem callers compatible.
328
- */
329
- export const DealDetailResponseSchema = DealListItemSchema.extend({
330
- conversation: DealConversationSchema,
331
- lineage: DealLineageSchema.optional()
332
- })
333
-
334
- /**
335
- * Single acq_deal_notes row (camelCase API representation).
336
- */
337
- export const DealNoteResponseSchema = z.object({
338
- id: z.string(),
339
- dealId: z.string(),
340
- organizationId: z.string(),
341
- authorUserId: z.string().nullable(),
342
- body: z.string(),
343
- createdAt: z.string(),
344
- updatedAt: z.string()
345
- })
346
-
347
- export const DealNoteListResponseSchema = z.array(DealNoteResponseSchema)
348
-
349
- /**
350
- * Single acq_deal_tasks row (camelCase API representation).
351
- * Matches AcqDealTask domain type from types.ts.
352
- */
353
- export const DealTaskResponseSchema = z.object({
354
- id: z.string(),
355
- organizationId: z.string(),
356
- dealId: z.string(),
357
- title: z.string(),
358
- description: z.string().nullable(),
359
- kind: AcqDealTaskKindSchema,
360
- dueAt: z.string().nullable(),
361
- assigneeUserId: z.string().nullable(),
362
- completedAt: z.string().nullable(),
363
- completedByUserId: z.string().nullable(),
364
- createdAt: z.string(),
365
- updatedAt: z.string(),
366
- createdByUserId: z.string().nullable()
367
- })
368
-
369
- export const DealTaskListResponseSchema = z.array(DealTaskResponseSchema)
370
-
371
- // ---------------------------------------------------------------------------
372
- // Bundled export
373
- // ---------------------------------------------------------------------------
374
-
375
- export const DealSchemas = {
376
- // Primitives
377
- CrmStageKey: CrmStageKeySchema,
378
- CrmStateKey: CrmStateKeySchema,
379
- DealStage: DealStageSchema,
380
-
381
- // Params
382
- DealIdParams: DealIdParamsSchema,
383
- DealTaskIdParams: DealTaskIdParamsSchema,
384
-
385
- // Queries
386
- ListDealsQuery: ListDealsQuerySchema,
387
- DealLookupQuery: DealLookupQuerySchema,
388
- ListDealTasksDueQuery: ListDealTasksDueQuerySchema,
389
-
390
- // Request bodies
391
- CreateDealNoteRequest: CreateDealNoteRequestSchema,
392
- CreateDealTaskRequest: CreateDealTaskRequestSchema,
393
- TransitionItemRequest: CrmTransitionItemRequestSchema,
394
- TransitionDealStateRequest: TransitionDealStateRequestSchema,
395
- ExecuteActionParams: ExecuteActionParamsSchema,
396
- ExecuteActionRequest: ExecuteActionRequestSchema,
397
-
398
- // Responses
399
- DealPriority: DealPrioritySchema,
400
- DealListResponse: DealListResponseSchema,
401
- DealSummaryResponse: DealSummaryResponseSchema,
402
- DealLookupResponse: DealLookupResponseSchema,
403
- ConversationMessage: ConversationMessageSchema,
404
- DealLineageListRef: DealLineageListRefSchema,
405
- DealLineageProjectRef: DealLineageProjectRefSchema,
406
- DealLineageClientRef: DealLineageClientRefSchema,
407
- DealLineage: DealLineageSchema,
408
- DealDetailResponse: DealDetailResponseSchema,
409
- DealNoteResponse: DealNoteResponseSchema,
410
- DealNoteListResponse: DealNoteListResponseSchema,
411
- DealTaskResponse: DealTaskResponseSchema,
412
- DealTaskListResponse: DealTaskListResponseSchema
413
- }
414
-
415
- // ---------------------------------------------------------------------------
416
- // Inferred types
417
- // ---------------------------------------------------------------------------
418
-
419
- export type DealStage = z.infer<typeof DealStageSchema>
420
- export type CrmStageKey = z.infer<typeof CrmStageKeySchema>
421
- export type CrmStateKey = z.infer<typeof CrmStateKeySchema>
422
- export type AcqDealTaskKind = z.infer<typeof AcqDealTaskKindSchema>
423
- export type DealIdParams = z.infer<typeof DealIdParamsSchema>
424
- export type DealTaskIdParams = z.infer<typeof DealTaskIdParamsSchema>
425
- export type ListDealsQuery = z.infer<typeof ListDealsQuerySchema>
426
- export type DealLookupQuery = z.infer<typeof DealLookupQuerySchema>
427
- export type ListDealTasksDueQuery = z.infer<typeof ListDealTasksDueQuerySchema>
428
- export type CreateDealNoteRequest = z.infer<typeof CreateDealNoteRequestSchema>
429
- export type CreateDealTaskRequest = z.infer<typeof CreateDealTaskRequestSchema>
430
- export type TransitionItemRequest = z.infer<typeof TransitionItemRequestSchema>
431
- export type CrmTransitionItemRequest = z.infer<typeof CrmTransitionItemRequestSchema>
432
- export type TransitionDealStateRequest = z.infer<typeof TransitionDealStateRequestSchema>
433
- export type ExecuteActionParams = z.infer<typeof ExecuteActionParamsSchema>
434
- export type ExecuteActionRequest = z.infer<typeof ExecuteActionRequestSchema>
435
- export type DealPriorityResponse = z.infer<typeof DealPrioritySchema>
436
- export type DealListResponse = z.infer<typeof DealListResponseSchema>
437
- export type DealSummaryResponse = z.infer<typeof DealSummaryResponseSchema>
438
- export type DealLookupItem = z.infer<typeof DealLookupItemSchema>
439
- export type DealLookupResponse = z.infer<typeof DealLookupResponseSchema>
440
- export type ConversationMessage = z.infer<typeof ConversationMessageSchema>
441
- export type DealLineageListRef = z.infer<typeof DealLineageListRefSchema>
442
- export type DealLineageProjectRef = z.infer<typeof DealLineageProjectRefSchema>
443
- export type DealLineageClientRef = z.infer<typeof DealLineageClientRefSchema>
444
- export type DealLineage = z.infer<typeof DealLineageSchema>
445
- export type DealDetailResponse = z.infer<typeof DealDetailResponseSchema>
446
- export type DealNoteResponse = z.infer<typeof DealNoteResponseSchema>
447
- export type DealNoteListResponse = z.infer<typeof DealNoteListResponseSchema>
448
- export type DealTaskResponse = z.infer<typeof DealTaskResponseSchema>
449
- export type DealTaskListResponse = z.infer<typeof DealTaskListResponseSchema>
450
-
451
- // ---------------------------------------------------------------------------
452
- // List Management API Schemas
453
- //
454
- // Request/response validation for /api/acquisition/lists surface.
455
- // Used by both the API route handlers and the frontend hooks.
456
- //
457
- // Table mapping:
458
- // acq_lists -> AcqListSchemas (list/detail/progress)
459
- // acq_list_companies -> AcqListSchemas (add/remove company membership)
460
- // acq_list_contacts -> AcqListSchemas (add/remove contact membership)
461
- // acq_list_executions -> AcqListSchemas (execution history)
462
- // ---------------------------------------------------------------------------
463
-
464
- // ---------------------------------------------------------------------------
465
- // Primitives — list status enum + jsonb config schemas
466
- // ---------------------------------------------------------------------------
467
-
468
- /**
469
- * Lifecycle status enum for `acq_lists.status` (mirrors DB CHECK constraint
470
- * from migration 20260428000003_lead_gen_acq_lists_status_and_config.sql).
471
- */
472
- export const ListStatusSchema = z.enum(['draft', 'enriching', 'launched', 'closing', 'archived'])
473
-
474
- /**
475
- * Scraping criteria stored in `acq_lists.scraping_config` jsonb.
476
- * Edited via the UI; consumed by lgn-01 prospecting workflows (Apify input shape,
477
- * geography, vertical, size). All fields are optional — empty config is valid.
478
- */
479
- export const ScrapingConfigSchema = z.object({
480
- vertical: z.string().trim().max(255).optional(),
481
- geography: z.string().trim().max(500).optional(),
482
- size: z.string().trim().max(255).optional(),
483
- apifyInput: z.record(z.string(), z.unknown()).optional()
484
- })
485
-
486
- /**
487
- * ICP / qualification rubric stored in `acq_lists.icp` jsonb.
488
- * Replaces the legacy `config.qualification` blob. Consumed by the
489
- * company-qualification workflow.
490
- */
491
- export const IcpRubricSchema = z.object({
492
- qualificationRubricKey: z.string().trim().max(255).nullish(),
493
- targetDescription: z.string().optional(),
494
- minReviewCount: z.number().int().min(0).optional(),
495
- minRating: z.number().min(0).max(5).optional(),
496
- excludeFranchises: z.boolean().optional(),
497
- customRules: z.string().optional()
498
- })
499
-
500
- /**
128
+ stageKey: CrmStageKeySchema,
129
+ stateKey: CrmStateKeySchema.nullable().optional(),
130
+ reason: z.string().optional(),
131
+ expectedUpdatedAt: z.string().datetime().optional()
132
+ })
133
+ .strict()
134
+
135
+ export const TransitionDealStateRequestSchema = z
136
+ .object({
137
+ stateKey: CrmStateKeySchema,
138
+ reason: z.string().optional(),
139
+ expectedUpdatedAt: z.string().datetime().optional()
140
+ })
141
+ .strict()
142
+
143
+ export const ExecuteActionParamsSchema = z
144
+ .object({
145
+ dealId: UuidSchema,
146
+ actionKey: NonEmptyStringSchema
147
+ })
148
+ .strict()
149
+
150
+ export const ExecuteActionRequestSchema = z
151
+ .object({
152
+ payload: z.record(z.string(), z.unknown()).optional()
153
+ })
154
+ .strict()
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // Response schemas (no .strict() — allows forward-compatible additions)
158
+ // ---------------------------------------------------------------------------
159
+
160
+ /**
161
+ * Contact summary nested inside DealListItem / DealDetailResponse.
162
+ * Matches the joined shape returned by useDeals / useDealDetail Supabase queries.
163
+ */
164
+ export const DealContactSummarySchema = z.object({
165
+ id: z.string(),
166
+ first_name: z.string().nullable(),
167
+ last_name: z.string().nullable(),
168
+ email: z.string(),
169
+ title: z.string().nullable(),
170
+ headline: z.string().nullable(),
171
+ linkedin_url: z.string().nullable(),
172
+ processing_state: ProcessingStateSchema.nullable(),
173
+ enrichment_data: z.record(z.string(), z.unknown()).nullable(),
174
+ company: z
175
+ .object({
176
+ id: z.string(),
177
+ name: z.string(),
178
+ domain: z.string().nullable(),
179
+ website: z.string().nullable(),
180
+ linkedin_url: z.string().nullable(),
181
+ segment: z.string().nullable(),
182
+ category: z.string().nullable(),
183
+ num_employees: z.number().nullable()
184
+ })
185
+ .nullable()
186
+ })
187
+
188
+ export const DealPrioritySchema = z.object({
189
+ bucketKey: z.enum(['needs_response', 'follow_up_due', 'waiting', 'stale', 'closed_low']),
190
+ rank: z.number().int(),
191
+ label: z.string(),
192
+ color: z.string(),
193
+ reason: z.string(),
194
+ latestActivityAt: z.string().nullable(),
195
+ nextActionAt: z.string().nullable()
196
+ })
197
+
198
+ /**
199
+ * Deal list item with joined contact (and company via contact).
200
+ * Matches DealListItem from @repo/core types.
201
+ */
202
+ export const DealListItemSchema = z.object({
203
+ // acq_deals columns
204
+ id: z.string(),
205
+ organization_id: z.string(),
206
+ client_id: z.string().nullable().optional(),
207
+ contact_id: z.string().nullable(),
208
+ contact_email: z.string(),
209
+ pipeline_key: z.string(),
210
+ stage_key: z.string().nullable(),
211
+ state_key: z.string().nullable(),
212
+ activity_log: z.unknown(),
213
+ discovery_data: z.unknown().nullable(),
214
+ discovery_submitted_at: z.string().nullable(),
215
+ discovery_submitted_by: z.string().nullable(),
216
+ proposal_data: z.unknown().nullable(),
217
+ proposal_sent_at: z.string().nullable(),
218
+ proposal_pdf_url: z.string().nullable(),
219
+ signature_envelope_id: z.string().nullable(),
220
+ source_list_id: z.string().nullable(),
221
+ source_type: z.string().nullable(),
222
+ initial_fee: z.number().nullable(),
223
+ monthly_fee: z.number().nullable(),
224
+ closed_lost_at: z.string().nullable(),
225
+ closed_lost_reason: z.string().nullable(),
226
+ created_at: z.string(),
227
+ updated_at: z.string(),
228
+ priority: DealPrioritySchema,
229
+ ownership: z.enum(['us', 'them']).nullable(),
230
+ nextAction: z.string().nullable(),
231
+ // joined relation
232
+ contact: DealContactSummarySchema.nullable()
233
+ })
234
+
235
+ export const DealListResponseSchema = z.object({
236
+ data: z.array(DealListItemSchema),
237
+ total: z.number().int(),
238
+ limit: z.number().int(),
239
+ offset: z.number().int()
240
+ })
241
+
242
+ export const DealStageSummarySchema = z.object({
243
+ stage: z.string(),
244
+ count: z.number().int(),
245
+ totalValue: z.number(),
246
+ oldestUpdatedAt: z.string().nullable(),
247
+ newestUpdatedAt: z.string().nullable()
248
+ })
249
+
250
+ export const StaleDealSummarySchema = z.object({
251
+ id: z.string(),
252
+ contactEmail: z.string(),
253
+ stageKey: z.string(),
254
+ updatedAt: z.string(),
255
+ daysStale: z.number().int()
256
+ })
257
+
258
+ export const DealSummaryResponseSchema = z.object({
259
+ totalDeals: z.number().int(),
260
+ openDeals: z.number().int(),
261
+ wonDeals: z.number().int(),
262
+ lostDeals: z.number().int(),
263
+ winRate: z.number(),
264
+ avgDealSize: z.number(),
265
+ totalPipelineValue: z.number(),
266
+ stageSummary: z.array(DealStageSummarySchema),
267
+ staleDeals: z.array(StaleDealSummarySchema)
268
+ })
269
+
270
+ export const DealLookupItemSchema = z.object({
271
+ id: z.string(),
272
+ contactEmail: z.string(),
273
+ stageKey: z.string().nullable(),
274
+ updatedAt: z.string(),
275
+ contactName: z.string().nullable(),
276
+ companyName: z.string().nullable(),
277
+ displayLabel: z.string()
278
+ })
279
+
280
+ export const DealLookupResponseSchema = z.array(DealLookupItemSchema)
281
+
282
+ export const ConversationMessageSchema = z.object({
283
+ id: z.string(),
284
+ direction: z.enum(['inbound', 'outbound']),
285
+ fromEmail: z.string(),
286
+ toEmail: z.string(),
287
+ subject: z.string().nullable(),
288
+ body: z.string(),
289
+ sentAt: z.string().nullable()
290
+ })
291
+
292
+ export const DealConversationSchema = z.object({
293
+ messages: z.array(ConversationMessageSchema)
294
+ })
295
+
296
+ export const DealLineageListRefSchema = z.object({
297
+ id: z.string(),
298
+ name: z.string(),
299
+ status: z.string()
300
+ })
301
+
302
+ export const DealLineageProjectRefSchema = z.object({
303
+ id: z.string(),
304
+ name: z.string(),
305
+ kind: z.string(),
306
+ status: z.string(),
307
+ updatedAt: z.string()
308
+ })
309
+
310
+ export const DealLineageClientRefSchema = z.object({
311
+ id: z.string(),
312
+ name: z.string(),
313
+ status: z.string()
314
+ })
315
+
316
+ export const DealLineageSchema = z.object({
317
+ list: DealLineageListRefSchema.nullable(),
318
+ projects: z.array(DealLineageProjectRefSchema),
319
+ client: DealLineageClientRefSchema.nullable()
320
+ })
321
+
322
+ /**
323
+ * Deal detail shape — currently the same as a list item (full joined record).
324
+ * Additive fields keep existing DealListItem callers compatible.
325
+ */
326
+ export const DealDetailResponseSchema = DealListItemSchema.extend({
327
+ conversation: DealConversationSchema,
328
+ lineage: DealLineageSchema.optional()
329
+ })
330
+
331
+ /**
332
+ * Single acq_deal_notes row (camelCase API representation).
333
+ */
334
+ export const DealNoteResponseSchema = z.object({
335
+ id: z.string(),
336
+ dealId: z.string(),
337
+ organizationId: z.string(),
338
+ authorUserId: z.string().nullable(),
339
+ body: z.string(),
340
+ createdAt: z.string(),
341
+ updatedAt: z.string()
342
+ })
343
+
344
+ export const DealNoteListResponseSchema = z.array(DealNoteResponseSchema)
345
+
346
+ /**
347
+ * Single acq_deal_tasks row (camelCase API representation).
348
+ * Matches AcqDealTask domain type from types.ts.
349
+ */
350
+ export const DealTaskResponseSchema = z.object({
351
+ id: z.string(),
352
+ organizationId: z.string(),
353
+ dealId: z.string(),
354
+ title: z.string(),
355
+ description: z.string().nullable(),
356
+ kind: AcqDealTaskKindSchema,
357
+ dueAt: z.string().nullable(),
358
+ assigneeUserId: z.string().nullable(),
359
+ completedAt: z.string().nullable(),
360
+ completedByUserId: z.string().nullable(),
361
+ createdAt: z.string(),
362
+ updatedAt: z.string(),
363
+ createdByUserId: z.string().nullable()
364
+ })
365
+
366
+ export const DealTaskListResponseSchema = z.array(DealTaskResponseSchema)
367
+
368
+ // ---------------------------------------------------------------------------
369
+ // Bundled export
370
+ // ---------------------------------------------------------------------------
371
+
372
+ export const DealSchemas = {
373
+ // Primitives
374
+ CrmStageKey: CrmStageKeySchema,
375
+ CrmStateKey: CrmStateKeySchema,
376
+ DealStage: DealStageSchema,
377
+
378
+ // Params
379
+ DealIdParams: DealIdParamsSchema,
380
+ DealTaskIdParams: DealTaskIdParamsSchema,
381
+
382
+ // Queries
383
+ ListDealsQuery: ListDealsQuerySchema,
384
+ DealLookupQuery: DealLookupQuerySchema,
385
+ ListDealTasksDueQuery: ListDealTasksDueQuerySchema,
386
+
387
+ // Request bodies
388
+ CreateDealNoteRequest: CreateDealNoteRequestSchema,
389
+ CreateDealTaskRequest: CreateDealTaskRequestSchema,
390
+ TransitionItemRequest: CrmTransitionItemRequestSchema,
391
+ TransitionDealStateRequest: TransitionDealStateRequestSchema,
392
+ ExecuteActionParams: ExecuteActionParamsSchema,
393
+ ExecuteActionRequest: ExecuteActionRequestSchema,
394
+
395
+ // Responses
396
+ DealPriority: DealPrioritySchema,
397
+ DealListResponse: DealListResponseSchema,
398
+ DealSummaryResponse: DealSummaryResponseSchema,
399
+ DealLookupResponse: DealLookupResponseSchema,
400
+ ConversationMessage: ConversationMessageSchema,
401
+ DealLineageListRef: DealLineageListRefSchema,
402
+ DealLineageProjectRef: DealLineageProjectRefSchema,
403
+ DealLineageClientRef: DealLineageClientRefSchema,
404
+ DealLineage: DealLineageSchema,
405
+ DealDetailResponse: DealDetailResponseSchema,
406
+ DealNoteResponse: DealNoteResponseSchema,
407
+ DealNoteListResponse: DealNoteListResponseSchema,
408
+ DealTaskResponse: DealTaskResponseSchema,
409
+ DealTaskListResponse: DealTaskListResponseSchema
410
+ }
411
+
412
+ // ---------------------------------------------------------------------------
413
+ // Inferred types
414
+ // ---------------------------------------------------------------------------
415
+
416
+ export type DealStage = z.infer<typeof DealStageSchema>
417
+ export type CrmStageKey = z.infer<typeof CrmStageKeySchema>
418
+ export type CrmStateKey = z.infer<typeof CrmStateKeySchema>
419
+ export type AcqDealTaskKind = z.infer<typeof AcqDealTaskKindSchema>
420
+ export type DealIdParams = z.infer<typeof DealIdParamsSchema>
421
+ export type DealTaskIdParams = z.infer<typeof DealTaskIdParamsSchema>
422
+ export type ListDealsQuery = z.infer<typeof ListDealsQuerySchema>
423
+ export type DealLookupQuery = z.infer<typeof DealLookupQuerySchema>
424
+ export type ListDealTasksDueQuery = z.infer<typeof ListDealTasksDueQuerySchema>
425
+ export type CreateDealNoteRequest = z.infer<typeof CreateDealNoteRequestSchema>
426
+ export type CreateDealTaskRequest = z.infer<typeof CreateDealTaskRequestSchema>
427
+ export type TransitionItemRequest = z.infer<typeof TransitionItemRequestSchema>
428
+ export type CrmTransitionItemRequest = z.infer<typeof CrmTransitionItemRequestSchema>
429
+ export type TransitionDealStateRequest = z.infer<typeof TransitionDealStateRequestSchema>
430
+ export type ExecuteActionParams = z.infer<typeof ExecuteActionParamsSchema>
431
+ export type ExecuteActionRequest = z.infer<typeof ExecuteActionRequestSchema>
432
+ export type DealPriorityResponse = z.infer<typeof DealPrioritySchema>
433
+ export type DealListResponse = z.infer<typeof DealListResponseSchema>
434
+ export type DealSummaryResponse = z.infer<typeof DealSummaryResponseSchema>
435
+ export type DealLookupItem = z.infer<typeof DealLookupItemSchema>
436
+ export type DealLookupResponse = z.infer<typeof DealLookupResponseSchema>
437
+ export type ConversationMessage = z.infer<typeof ConversationMessageSchema>
438
+ export type DealLineageListRef = z.infer<typeof DealLineageListRefSchema>
439
+ export type DealLineageProjectRef = z.infer<typeof DealLineageProjectRefSchema>
440
+ export type DealLineageClientRef = z.infer<typeof DealLineageClientRefSchema>
441
+ export type DealLineage = z.infer<typeof DealLineageSchema>
442
+ export type DealDetailResponse = z.infer<typeof DealDetailResponseSchema>
443
+ export type DealNoteResponse = z.infer<typeof DealNoteResponseSchema>
444
+ export type DealNoteListResponse = z.infer<typeof DealNoteListResponseSchema>
445
+ export type DealTaskResponse = z.infer<typeof DealTaskResponseSchema>
446
+ export type DealTaskListResponse = z.infer<typeof DealTaskListResponseSchema>
447
+
448
+ // ---------------------------------------------------------------------------
449
+ // List Management API Schemas
450
+ //
451
+ // Request/response validation for /api/acquisition/lists surface.
452
+ // Used by both the API route handlers and the frontend hooks.
453
+ //
454
+ // Table mapping:
455
+ // acq_lists -> AcqListSchemas (list/detail/progress)
456
+ // acq_list_companies -> AcqListSchemas (add/remove company membership)
457
+ // acq_list_contacts -> AcqListSchemas (add/remove contact membership)
458
+ // acq_list_executions -> AcqListSchemas (execution history)
459
+ // ---------------------------------------------------------------------------
460
+
461
+ // ---------------------------------------------------------------------------
462
+ // Primitives — list status enum + jsonb config schemas
463
+ // ---------------------------------------------------------------------------
464
+
465
+ /**
466
+ * Lifecycle status enum for `acq_lists.status` (mirrors DB CHECK constraint
467
+ * from migration 20260428000003_lead_gen_acq_lists_status_and_config.sql).
468
+ */
469
+ export const ListStatusSchema = z.enum(['draft', 'enriching', 'launched', 'closing', 'archived'])
470
+
471
+ /**
472
+ * Scraping criteria stored in `acq_lists.scraping_config` jsonb.
473
+ * Edited via the UI; consumed by lgn-01 prospecting workflows (Apify input shape,
474
+ * geography, vertical, size). All fields are optional — empty config is valid.
475
+ */
476
+ export const ScrapingConfigSchema = z.object({
477
+ vertical: z.string().trim().max(255).optional(),
478
+ geography: z.string().trim().max(500).optional(),
479
+ size: z.string().trim().max(255).optional(),
480
+ apifyInput: z.record(z.string(), z.unknown()).optional()
481
+ })
482
+
483
+ /**
484
+ * ICP / qualification rubric stored in `acq_lists.icp` jsonb.
485
+ * Replaces the legacy `config.qualification` blob. Consumed by the
486
+ * company-qualification workflow.
487
+ */
488
+ export const IcpRubricSchema = z.object({
489
+ qualificationRubricKey: z.string().trim().max(255).nullish(),
490
+ targetDescription: z.string().optional(),
491
+ minReviewCount: z.number().int().min(0).optional(),
492
+ minRating: z.number().min(0).max(5).optional(),
493
+ excludeFranchises: z.boolean().optional(),
494
+ customRules: z.string().optional()
495
+ })
496
+
497
+ /**
501
498
  * One stage entry in a list's `pipeline_config.stages[]`.
502
499
  *
503
500
  * Stage catalogs are model-owned. The published core schema validates the
@@ -506,673 +503,673 @@ export const IcpRubricSchema = z.object({
506
503
  */
507
504
  export const PipelineStageSchema = z.object({
508
505
  key: LeadGenStageKeySchema,
509
- label: z.string().optional(),
510
- enabled: z.boolean().optional(),
511
- order: z.number().int().optional()
512
- })
513
-
514
- /**
515
- * Pipeline presentation contract stored in `acq_lists.pipeline_config` jsonb.
516
- * `stages[].key` validates against the catalog; the rest is presentation only.
517
- */
518
- export const PipelineConfigSchema = z.object({
519
- stages: z.array(PipelineStageSchema).optional()
520
- })
521
-
522
- export const BuildPlanSnapshotStepSchema = z
523
- .object({
524
- id: z.string().trim().min(1).max(100),
525
- label: z.string().trim().min(1).max(120),
526
- description: z.string().trim().min(1).max(2000).optional(),
527
- primaryEntity: z.enum(['company', 'contact']),
528
- outputs: z.array(z.enum(['company', 'contact', 'export'])).min(1),
529
- stageKey: LeadGenStageKeySchema,
530
- recordEntity: z.enum(['company', 'contact']).optional(),
531
- recordsStageKey: LeadGenStageKeySchema.optional(),
532
- recordSourceStageKey: LeadGenStageKeySchema.optional(),
533
- dependsOn: z.array(z.string().trim().min(1).max(100)).optional(),
534
- dependencyMode: z.literal('per-record-eligibility'),
535
- actionKey: LeadGenActionKeySchema,
536
- defaultBatchSize: z.number().int().positive(),
537
- maxBatchSize: z.number().int().positive(),
538
- recordColumns: z
539
- .object({
540
- company: z.array(RecordColumnConfigSchema).optional(),
541
- contact: z.array(RecordColumnConfigSchema).optional()
542
- })
543
- .optional(),
544
- credentialRequirements: z.array(CredentialRequirementSchema).optional()
545
- })
546
- .refine((step) => step.defaultBatchSize <= step.maxBatchSize, {
547
- message: 'defaultBatchSize must be less than or equal to maxBatchSize',
548
- path: ['defaultBatchSize']
549
- })
550
-
551
- export const BuildPlanSnapshotSchema = z
552
- .object({
553
- templateId: z.string().trim().min(1).max(100),
554
- templateLabel: z.string().trim().min(1).max(120),
555
- steps: z.array(BuildPlanSnapshotStepSchema).min(1)
556
- })
557
- .superRefine((snapshot, ctx) => {
558
- const stepIds = new Set<string>()
559
-
560
- snapshot.steps.forEach((step, index) => {
561
- if (stepIds.has(step.id)) {
562
- ctx.addIssue({
563
- code: z.ZodIssueCode.custom,
564
- message: `duplicate build-plan step id "${step.id}"`,
565
- path: ['steps', index, 'id']
566
- })
567
- }
568
- stepIds.add(step.id)
569
- })
570
-
571
- snapshot.steps.forEach((step, index) => {
572
- for (const dependencyId of step.dependsOn ?? []) {
573
- if (!stepIds.has(dependencyId)) {
574
- ctx.addIssue({
575
- code: z.ZodIssueCode.custom,
576
- message: `dependsOn references unknown build-plan step "${dependencyId}"`,
577
- path: ['steps', index, 'dependsOn']
578
- })
579
- }
580
- }
581
- })
582
- })
583
-
584
- export const AcqListMetadataSchema = z
585
- .object({
586
- buildPlanSnapshot: BuildPlanSnapshotSchema.optional()
587
- })
588
- .catchall(z.unknown())
589
-
590
- export const ProspectingBuildTemplateIdSchema = z.string().trim().min(1).max(100).refine(isProspectingBuildTemplateId, {
591
- message: 'buildTemplateId must match a known prospecting build template'
592
- })
593
-
594
- // ---------------------------------------------------------------------------
595
- // List telemetry / progress schemas
596
- // ---------------------------------------------------------------------------
597
-
598
- export const ListStageCountsSchema = z.object({
599
- // Attempted counts by canonical lead-gen stage. The detailed status
600
- // distribution lives on ListProgress; telemetry keeps the overview payload small.
601
- stageCounts: z.object({
602
- populated: z.number().int(),
603
- extracted: z.number().int(),
604
- qualified: z.number().int(),
605
- discovered: z.number().int(),
606
- verified: z.number().int(),
607
- personalized: z.number().int(),
608
- uploaded: z.number().int()
609
- }),
610
- deliverability: z.object({
611
- valid: z.number().int(),
612
- risky: z.number().int(),
613
- invalid: z.number().int(),
614
- unknown: z.number().int(),
615
- bounced: z.number().int()
616
- })
617
- })
618
-
619
- export const ListTelemetrySchema = z.object({
620
- listId: UuidSchema,
621
- totalCompanies: z.number().int(),
622
- totalContacts: z.number().int(),
623
- stageCounts: ListStageCountsSchema.shape.stageCounts,
624
- deliverability: ListStageCountsSchema.shape.deliverability,
625
- activeWorkflows: z.array(z.string()).optional()
626
- })
627
-
628
- // ---------------------------------------------------------------------------
629
- // Params
630
- // ---------------------------------------------------------------------------
631
-
632
- export const ListIdParamsSchema = z.object({
633
- listId: UuidSchema
634
- })
635
-
636
- // ---------------------------------------------------------------------------
637
- // Request body schemas (all use .strict() — rejects unknown fields)
638
- // ---------------------------------------------------------------------------
639
-
640
- export const CreateListRequestSchema = z
641
- .object({
642
- name: z.string().trim().min(1).max(255),
643
- description: z.string().trim().nullable().optional(),
644
- status: ListStatusSchema.optional(),
645
- buildTemplateId: ProspectingBuildTemplateIdSchema.optional(),
646
- scrapingConfig: ScrapingConfigSchema.optional(),
647
- icp: IcpRubricSchema.optional(),
648
- pipelineConfig: PipelineConfigSchema.optional()
649
- })
650
- .strict()
651
-
652
- export const UpdateListRequestSchema = z
653
- .object({
654
- name: z.string().trim().min(1).max(255).optional(),
655
- description: z.string().trim().nullable().optional(),
656
- batchIds: z.array(z.string()).optional(),
657
- buildTemplateId: ProspectingBuildTemplateIdSchema.optional(),
658
- confirmBuildTemplateChange: z.literal(true).optional()
659
- })
660
- .strict()
661
- .refine(
662
- (data) =>
663
- data.name !== undefined ||
664
- data.description !== undefined ||
665
- data.batchIds !== undefined ||
666
- data.buildTemplateId !== undefined,
667
- {
668
- message: 'At least one field (name, description, batchIds, or buildTemplateId) must be provided'
669
- }
670
- )
671
- .refine((data) => data.buildTemplateId === undefined || data.confirmBuildTemplateChange === true, {
672
- message: 'confirmBuildTemplateChange must be true when changing buildTemplateId',
673
- path: ['confirmBuildTemplateChange']
674
- })
675
-
676
- /**
677
- * Status-only PATCH body for `/acquisition/lists/:listId/status`.
678
- * Replaces the previous `transitionList` flow.
679
- */
680
- export const UpdateListStatusRequestSchema = z
681
- .object({
682
- status: ListStatusSchema
683
- })
684
- .strict()
685
-
686
- /**
687
- * Partial patch for the three jsonb config columns. UI sends only the edited
688
- * subtree; server writes the field as-is (no deep merge — each column is
689
- * replaced atomically when present in the patch).
690
- */
691
- export const UpdateListConfigRequestSchema = z
692
- .object({
693
- scrapingConfig: ScrapingConfigSchema.partial().optional(),
694
- icp: IcpRubricSchema.partial().optional(),
695
- pipelineConfig: PipelineConfigSchema.partial().optional()
696
- })
697
- .strict()
698
- .refine((data) => data.scrapingConfig !== undefined || data.icp !== undefined || data.pipelineConfig !== undefined, {
699
- message: 'At least one of scrapingConfig, icp, or pipelineConfig must be provided'
700
- })
701
-
702
- export const AddCompaniesToListRequestSchema = z
703
- .object({
704
- companyIds: z.array(UuidSchema).min(1).max(1000)
705
- })
706
- .strict()
707
-
708
- export const RemoveCompaniesFromListRequestSchema = z
709
- .object({
710
- companyIds: z.array(UuidSchema).min(1).max(1000)
711
- })
712
- .strict()
713
-
714
- export const AddContactsToListRequestSchema = z
715
- .object({
716
- contactIds: z.array(UuidSchema).min(1).max(1000)
717
- })
718
- .strict()
719
-
720
- export const RecordListExecutionRequestSchema = z
721
- .object({
722
- executionId: UuidSchema,
723
- configSnapshot: z.record(z.string(), z.unknown()).optional()
724
- })
725
- .strict()
726
-
727
- // ---------------------------------------------------------------------------
728
- // Response schemas (no .strict() — allows forward-compatible additions)
729
- // ---------------------------------------------------------------------------
730
-
731
- /**
732
- * Single list as returned by /api/acquisition/lists/:id etc.
733
- * Camel-cased domain shape matching AcqList in types.ts.
734
- */
735
- export const AcqListResponseSchema = z.object({
736
- id: z.string(),
737
- organizationId: z.string(),
738
- name: z.string(),
739
- description: z.string().nullable(),
740
- batchIds: z.array(z.string()),
741
- instantlyCampaignId: z.string().nullable(),
742
- /** Lifecycle status (draft | enriching | launched | closing | archived). */
743
- status: ListStatusSchema,
744
- metadata: AcqListMetadataSchema,
745
- launchedAt: z.string().nullable(),
746
- completedAt: z.string().nullable(),
747
- createdAt: z.string(),
748
- /** Scraping criteria stored as jsonb on the row. */
749
- scrapingConfig: ScrapingConfigSchema,
750
- /** ICP / qualification rubric stored as jsonb on the row. */
751
- icp: IcpRubricSchema,
752
- /** Pipeline presentation contract stored as jsonb on the row. */
753
- pipelineConfig: PipelineConfigSchema
754
- })
755
-
756
- export const AcqListListResponseSchema = z.array(AcqListResponseSchema)
757
-
758
- export const ListTelemetryResponseSchema = ListTelemetrySchema
759
-
760
- export const ListTelemetryListResponseSchema = z.array(ListTelemetrySchema)
761
-
762
- const QueryBooleanSchema = z.preprocess((value) => {
763
- if (value === 'true' || value === '1' || value === true) return true
764
- if (value === 'false' || value === '0' || value === false) return false
765
- return value
766
- }, z.boolean())
767
-
768
- export const ListReadQuerySchema = z
769
- .object({
770
- status: ListStatusSchema.optional(),
771
- batch: z.string().trim().min(1).max(255).optional(),
772
- vertical: z.string().trim().min(1).max(255).optional(),
773
- limit: z.coerce.number().int().min(1).max(500).optional(),
774
- offset: z.coerce.number().int().min(0).optional()
775
- })
776
- .strict()
777
-
778
- export const GetListQuerySchema = z
779
- .object({
780
- includeDeals: QueryBooleanSchema.default(true),
781
- includeProgress: QueryBooleanSchema.default(false),
782
- dealLimit: z.coerce.number().int().min(0).max(100).default(25)
783
- })
784
- .strict()
785
-
786
- /**
787
- * Per-stage progress aggregate for a single pipeline stage.
788
- * `attempted` counts terminal statuses, including success, no-result, skipped,
789
- * error, and tolerant-reader `other` values.
790
- * `total` = total member/company count for the list.
791
- */
792
- export const ListStageProgressSchema = z.object({
793
- total: z.number().int().min(0),
794
- attempted: z.number().int().min(0),
795
- success: z.number().int().min(0),
796
- noResult: z.number().int().min(0),
797
- skipped: z.number().int().min(0),
798
- error: z.number().int().min(0),
799
- other: z.number().int().min(0),
800
- notAttempted: z.number().int().min(0)
801
- })
802
-
803
- /**
804
- * Progress response for GET /acquisition/lists/:listId/progress.
805
- * Aggregated on-demand from processing_state status values.
806
- * Stage keys are discovered from observed processing_state keys.
807
- */
808
- export const ListProgressResponseSchema = z.object({
809
- totalMembers: z.number().int().min(0),
810
- totalCompanies: z.number().int().min(0),
811
- byCompanyStage: z.record(z.string(), ListStageProgressSchema),
812
- byContactStage: z.record(z.string(), ListStageProgressSchema)
813
- })
814
-
815
- export const AcqListDealRefSchema = z.object({
816
- id: z.string(),
817
- contactEmail: z.string(),
818
- stageKey: z.string().nullable(),
819
- stateKey: z.string().nullable(),
820
- sourceType: z.string().nullable(),
821
- lastActivityAt: z.string()
822
- })
823
-
824
- export const AcqListLineageSchema = z.object({
825
- deals: z.object({
826
- total: z.number().int().min(0),
827
- refs: z.array(AcqListDealRefSchema),
828
- truncated: z.boolean()
829
- })
830
- })
831
-
832
- export const AcqListDetailResponseSchema = AcqListResponseSchema.extend({
833
- lineage: AcqListLineageSchema.optional(),
834
- progress: ListProgressResponseSchema.optional()
835
- })
836
-
837
- export const AcqListStatusListItemSchema = z.object({
838
- listId: z.string(),
839
- name: z.string(),
840
- status: ListStatusSchema,
841
- totalCompanies: z.number().int().min(0),
842
- totalContacts: z.number().int().min(0),
843
- totalDeals: z.number().int().min(0),
844
- createdAt: z.string()
845
- })
846
-
847
- export const AcqListStatusResponseSchema = z.object({
848
- totalLists: z.number().int().min(0),
849
- totalCompanies: z.number().int().min(0),
850
- totalContacts: z.number().int().min(0),
851
- totalDeals: z.number().int().min(0),
852
- byStatus: z.record(z.string(), z.number().int().min(0)),
853
- lists: z.array(AcqListStatusListItemSchema)
854
- })
855
-
856
- /**
857
- * Row from acq_list_executions joined with the execution summary,
858
- * shaped for the /lists/:id/executions response.
859
- */
860
- export const ListExecutionSummarySchema = z.object({
861
- executionId: z.string(),
862
- resourceId: z.string(),
863
- status: z.string(),
864
- createdAt: z.string(),
865
- completedAt: z.string().nullable(),
866
- durationMs: z.number().int().nullable(),
867
- input: z.unknown().nullable().optional()
868
- })
869
-
870
- export const ListExecutionsResponseSchema = z.array(ListExecutionSummarySchema)
871
-
872
- // ---------------------------------------------------------------------------
873
- // Company / Contact API Schemas
874
- // ---------------------------------------------------------------------------
875
-
876
- export const AcqCompanyStatusSchema = z.enum(['active', 'invalid'])
877
- export const AcqContactStatusSchema = z.enum(['active', 'invalid'])
878
- export const AcqEmailValidSchema = z.enum(['VALID', 'INVALID', 'RISKY', 'UNKNOWN'])
879
-
880
- export const CompanyIdParamsSchema = z.object({
881
- companyId: UuidSchema
882
- })
883
-
884
- export const ContactIdParamsSchema = z.object({
885
- contactId: UuidSchema
886
- })
887
-
888
- export const ListCompaniesQuerySchema = z
889
- .object({
890
- search: z.string().trim().min(1).max(200).optional(),
891
- listId: UuidSchema.optional(),
892
- domain: z.string().trim().min(1).max(255).optional(),
893
- website: z.string().trim().min(1).max(2048).optional(),
894
- segment: z.string().trim().min(1).max(255).optional(),
895
- category: z.string().trim().min(1).max(255).optional(),
896
- pipelineStatus: z.unknown().optional(),
897
- batchId: z.string().trim().min(1).max(255).optional(),
898
- status: AcqCompanyStatusSchema.optional(),
899
- includeAll: QueryBooleanSchema.optional(),
900
- limit: z.coerce.number().int().min(1).max(5000).default(50),
901
- offset: z.coerce.number().int().min(0).default(0)
902
- })
903
- .strict()
904
-
905
- export const ListContactsQuerySchema = z
906
- .object({
907
- search: z.string().trim().min(1).max(200).optional(),
908
- listId: UuidSchema.optional(),
909
- openingLineIsNull: QueryBooleanSchema.optional(),
910
- batchId: z.string().trim().min(1).max(255).optional(),
911
- contactStatus: AcqContactStatusSchema.optional(),
912
- limit: z.coerce.number().int().min(1).max(5000).default(5000),
913
- offset: z.coerce.number().int().min(0).default(0)
914
- })
915
- .strict()
916
-
917
- export const CreateCompanyRequestSchema = z
918
- .object({
919
- name: z.string().trim().min(1).max(255),
920
- clientId: UuidSchema.nullable().optional(),
921
- domain: z.string().trim().min(1).max(255).optional(),
922
- linkedinUrl: z.string().trim().url().optional(),
923
- website: z.string().trim().url().optional(),
924
- numEmployees: z.number().int().min(0).optional(),
925
- foundedYear: z.number().int().optional(),
926
- locationCity: z.string().trim().min(1).max(255).optional(),
927
- locationState: z.string().trim().min(1).max(255).optional(),
928
- category: z.string().trim().min(1).max(255).optional(),
929
- source: z.string().trim().min(1).max(255).optional(),
930
- batchId: z.string().trim().min(1).max(255).optional(),
931
- pipelineStatus: z.unknown().optional(),
932
- verticalResearch: z.string().trim().min(1).max(5000).optional()
933
- })
934
- .strict()
935
-
936
- export const UpdateCompanyRequestSchema = z
937
- .object({
938
- name: z.string().trim().min(1).max(255).optional(),
939
- clientId: UuidSchema.nullable().optional(),
940
- domain: z.string().trim().min(1).max(255).optional(),
941
- linkedinUrl: z.string().trim().url().optional(),
942
- website: z.string().trim().url().optional(),
943
- numEmployees: z.number().int().min(0).optional(),
944
- foundedYear: z.number().int().optional(),
945
- locationCity: z.string().trim().min(1).max(255).optional(),
946
- locationState: z.string().trim().min(1).max(255).optional(),
947
- category: z.string().trim().min(1).max(255).optional(),
948
- segment: z.string().trim().min(1).max(255).optional(),
949
- processingState: CompanyProcessingStateSchema.optional(),
950
- pipelineStatus: z.unknown().optional(),
951
- enrichmentData: z.record(z.string(), z.unknown()).optional(),
952
- source: z.string().trim().min(1).max(255).optional(),
953
- batchId: z.string().trim().min(1).max(255).optional(),
954
- status: AcqCompanyStatusSchema.optional(),
955
- verticalResearch: z.string().trim().min(1).max(5000).nullable().optional()
956
- })
957
- .strict()
958
- .refine(
959
- (data) =>
960
- data.name !== undefined ||
961
- data.clientId !== undefined ||
962
- data.domain !== undefined ||
963
- data.linkedinUrl !== undefined ||
964
- data.website !== undefined ||
965
- data.numEmployees !== undefined ||
966
- data.foundedYear !== undefined ||
967
- data.locationCity !== undefined ||
968
- data.locationState !== undefined ||
969
- data.category !== undefined ||
970
- data.segment !== undefined ||
971
- data.processingState !== undefined ||
972
- data.pipelineStatus !== undefined ||
973
- data.enrichmentData !== undefined ||
974
- data.source !== undefined ||
975
- data.batchId !== undefined ||
976
- data.status !== undefined ||
977
- data.verticalResearch !== undefined,
978
- {
979
- message: 'At least one field must be provided'
980
- }
981
- )
982
-
983
- export const CreateContactRequestSchema = z
984
- .object({
985
- email: z.string().trim().email(),
986
- clientId: UuidSchema.nullable().optional(),
987
- companyId: UuidSchema.optional(),
988
- firstName: z.string().trim().min(1).max(255).optional(),
989
- lastName: z.string().trim().min(1).max(255).optional(),
990
- linkedinUrl: z.string().trim().url().optional(),
991
- title: z.string().trim().min(1).max(255).optional(),
992
- source: z.string().trim().min(1).max(255).optional(),
993
- sourceId: z.string().trim().min(1).max(255).optional(),
994
- batchId: z.string().trim().min(1).max(255).optional(),
995
- pipelineStatus: z.unknown().optional()
996
- })
997
- .strict()
998
-
999
- export const UpdateContactRequestSchema = z
1000
- .object({
1001
- companyId: UuidSchema.optional(),
1002
- clientId: UuidSchema.nullable().optional(),
1003
- emailValid: AcqEmailValidSchema.optional(),
1004
- firstName: z.string().trim().min(1).max(255).optional(),
1005
- lastName: z.string().trim().min(1).max(255).optional(),
1006
- linkedinUrl: z.string().trim().url().optional(),
1007
- title: z.string().trim().min(1).max(255).optional(),
1008
- headline: z.string().trim().min(1).max(5000).optional(),
1009
- filterReason: z.string().trim().min(1).max(5000).optional(),
1010
- openingLine: z.string().trim().min(1).max(5000).optional(),
1011
- processingState: ContactProcessingStateSchema.optional(),
1012
- pipelineStatus: z.unknown().optional(),
1013
- enrichmentData: z.record(z.string(), z.unknown()).optional(),
1014
- status: AcqContactStatusSchema.optional()
1015
- })
1016
- .strict()
1017
- .refine(
1018
- (data) =>
1019
- data.companyId !== undefined ||
1020
- data.clientId !== undefined ||
1021
- data.emailValid !== undefined ||
1022
- data.firstName !== undefined ||
1023
- data.lastName !== undefined ||
1024
- data.linkedinUrl !== undefined ||
1025
- data.title !== undefined ||
1026
- data.headline !== undefined ||
1027
- data.filterReason !== undefined ||
1028
- data.openingLine !== undefined ||
1029
- data.processingState !== undefined ||
1030
- data.pipelineStatus !== undefined ||
1031
- data.enrichmentData !== undefined ||
1032
- data.status !== undefined,
1033
- {
1034
- message: 'At least one field must be provided'
1035
- }
1036
- )
1037
-
1038
- export const AcqCompanyResponseSchema = z.object({
1039
- id: z.string(),
1040
- organizationId: z.string(),
1041
- clientId: z.string().nullable().optional(),
1042
- name: z.string(),
1043
- domain: z.string().nullable(),
1044
- linkedinUrl: z.string().nullable(),
1045
- website: z.string().nullable(),
1046
- numEmployees: z.number().nullable(),
1047
- foundedYear: z.number().nullable(),
1048
- locationCity: z.string().nullable(),
1049
- locationState: z.string().nullable(),
1050
- category: z.string().nullable(),
1051
- categoryPain: z.string().nullable(),
1052
- segment: z.string().nullable(),
1053
- processingState: CompanyProcessingStateSchema.nullable(),
1054
- pipelineStatus: z.unknown().nullable().optional(),
1055
- enrichmentData: z.record(z.string(), z.unknown()).nullable(),
1056
- source: z.string().nullable(),
1057
- batchId: z.string().nullable(),
1058
- status: AcqCompanyStatusSchema,
1059
- contactCount: z.number().int().min(0),
1060
- verticalResearch: z.string().nullable(),
1061
- createdAt: z.string(),
1062
- updatedAt: z.string()
1063
- })
1064
-
1065
- export const AcqCompanyListResponseSchema = z.object({
1066
- data: z.array(AcqCompanyResponseSchema),
1067
- total: z.number().int(),
1068
- limit: z.number().int(),
1069
- offset: z.number().int()
1070
- })
1071
-
1072
- export const AcqCompanyFacetsResponseSchema = z.object({
1073
- segments: z.array(z.string()),
1074
- categories: z.array(z.string()),
1075
- statuses: z.array(AcqCompanyStatusSchema)
1076
- })
1077
-
1078
- export const AcqContactCompanySummarySchema = z.object({
1079
- id: z.string(),
1080
- name: z.string(),
1081
- domain: z.string().nullable(),
1082
- website: z.string().nullable(),
1083
- linkedinUrl: z.string().nullable(),
1084
- segment: z.string().nullable(),
1085
- category: z.string().nullable(),
1086
- status: AcqCompanyStatusSchema
1087
- })
1088
-
1089
- export const AcqContactResponseSchema = z.object({
1090
- id: z.string(),
1091
- organizationId: z.string(),
1092
- clientId: z.string().nullable().optional(),
1093
- companyId: z.string().nullable(),
1094
- email: z.string(),
1095
- emailValid: AcqEmailValidSchema.nullable(),
1096
- firstName: z.string().nullable(),
1097
- lastName: z.string().nullable(),
1098
- linkedinUrl: z.string().nullable(),
1099
- title: z.string().nullable(),
1100
- headline: z.string().nullable(),
1101
- filterReason: z.string().nullable(),
1102
- openingLine: z.string().nullable(),
1103
- source: z.string().nullable(),
1104
- sourceId: z.string().nullable(),
1105
- processingState: ContactProcessingStateSchema.nullable(),
1106
- pipelineStatus: z.unknown().nullable().optional(),
1107
- enrichmentData: z.record(z.string(), z.unknown()).nullable(),
1108
- attioPersonId: z.string().nullable(),
1109
- batchId: z.string().nullable(),
1110
- status: AcqContactStatusSchema,
1111
- company: AcqContactCompanySummarySchema.nullable().optional(),
1112
- createdAt: z.string(),
1113
- updatedAt: z.string()
1114
- })
1115
-
1116
- export const AcqContactListResponseSchema = z.object({
1117
- data: z.array(AcqContactResponseSchema),
1118
- total: z.number().int(),
1119
- limit: z.number().int(),
1120
- offset: z.number().int()
1121
- })
1122
-
1123
- // ---------------------------------------------------------------------------
1124
- // Track A: Artifacts API Schemas
1125
- // ---------------------------------------------------------------------------
1126
-
1127
- export const AcqArtifactOwnerKindSchema = z.enum(['company', 'contact', 'deal', 'list', 'list_member'])
1128
-
1129
- export const ListArtifactsQuerySchema = z
1130
- .object({
1131
- ownerKind: AcqArtifactOwnerKindSchema,
1132
- ownerId: UuidSchema
1133
- })
1134
- .strict()
1135
-
1136
- export const CreateArtifactRequestSchema = z
1137
- .object({
1138
- ownerKind: AcqArtifactOwnerKindSchema,
1139
- ownerId: UuidSchema,
1140
- kind: z.string().trim().min(1).max(255),
1141
- content: z.record(z.string(), z.unknown()),
1142
- sourceExecutionId: UuidSchema.optional()
1143
- })
1144
- .strict()
1145
-
1146
- export const AcqArtifactResponseSchema = z.object({
1147
- id: z.string(),
1148
- organizationId: z.string(),
1149
- ownerKind: z.string(),
1150
- ownerId: z.string(),
1151
- kind: z.string(),
1152
- content: z.record(z.string(), z.unknown()),
1153
- sourceExecutionId: z.string().nullable(),
1154
- createdBy: z.string().nullable(),
1155
- createdAt: z.string(),
1156
- version: z.number().int()
1157
- })
1158
-
1159
- export const AcqArtifactListResponseSchema = z.object({
1160
- artifacts: z.array(AcqArtifactResponseSchema)
1161
- })
1162
-
1163
- // ---------------------------------------------------------------------------
1164
- // Track B: List Members API Schemas
1165
- // ---------------------------------------------------------------------------
1166
-
1167
- export const ListMembersQuerySchema = z
1168
- .object({
1169
- limit: z.coerce.number().int().min(1).max(500).default(50),
1170
- offset: z.coerce.number().int().min(0).default(0)
1171
- })
1172
- .strict()
1173
-
1174
- export const ListRecordEntitySchema = z.enum(['company', 'contact'])
1175
-
506
+ label: z.string().optional(),
507
+ enabled: z.boolean().optional(),
508
+ order: z.number().int().optional()
509
+ })
510
+
511
+ /**
512
+ * Pipeline presentation contract stored in `acq_lists.pipeline_config` jsonb.
513
+ * `stages[].key` validates against the catalog; the rest is presentation only.
514
+ */
515
+ export const PipelineConfigSchema = z.object({
516
+ stages: z.array(PipelineStageSchema).optional()
517
+ })
518
+
519
+ export const BuildPlanSnapshotStepSchema = z
520
+ .object({
521
+ id: z.string().trim().min(1).max(100),
522
+ label: z.string().trim().min(1).max(120),
523
+ description: z.string().trim().min(1).max(2000).optional(),
524
+ primaryEntity: z.enum(['company', 'contact']),
525
+ outputs: z.array(z.enum(['company', 'contact', 'export'])).min(1),
526
+ stageKey: LeadGenStageKeySchema,
527
+ recordEntity: z.enum(['company', 'contact']).optional(),
528
+ recordsStageKey: LeadGenStageKeySchema.optional(),
529
+ recordSourceStageKey: LeadGenStageKeySchema.optional(),
530
+ dependsOn: z.array(z.string().trim().min(1).max(100)).optional(),
531
+ dependencyMode: z.literal('per-record-eligibility'),
532
+ actionKey: LeadGenActionKeySchema,
533
+ defaultBatchSize: z.number().int().positive(),
534
+ maxBatchSize: z.number().int().positive(),
535
+ recordColumns: z
536
+ .object({
537
+ company: z.array(RecordColumnConfigSchema).optional(),
538
+ contact: z.array(RecordColumnConfigSchema).optional()
539
+ })
540
+ .optional(),
541
+ credentialRequirements: z.array(CredentialRequirementSchema).optional()
542
+ })
543
+ .refine((step) => step.defaultBatchSize <= step.maxBatchSize, {
544
+ message: 'defaultBatchSize must be less than or equal to maxBatchSize',
545
+ path: ['defaultBatchSize']
546
+ })
547
+
548
+ export const BuildPlanSnapshotSchema = z
549
+ .object({
550
+ templateId: z.string().trim().min(1).max(100),
551
+ templateLabel: z.string().trim().min(1).max(120),
552
+ steps: z.array(BuildPlanSnapshotStepSchema).min(1)
553
+ })
554
+ .superRefine((snapshot, ctx) => {
555
+ const stepIds = new Set<string>()
556
+
557
+ snapshot.steps.forEach((step, index) => {
558
+ if (stepIds.has(step.id)) {
559
+ ctx.addIssue({
560
+ code: z.ZodIssueCode.custom,
561
+ message: `duplicate build-plan step id "${step.id}"`,
562
+ path: ['steps', index, 'id']
563
+ })
564
+ }
565
+ stepIds.add(step.id)
566
+ })
567
+
568
+ snapshot.steps.forEach((step, index) => {
569
+ for (const dependencyId of step.dependsOn ?? []) {
570
+ if (!stepIds.has(dependencyId)) {
571
+ ctx.addIssue({
572
+ code: z.ZodIssueCode.custom,
573
+ message: `dependsOn references unknown build-plan step "${dependencyId}"`,
574
+ path: ['steps', index, 'dependsOn']
575
+ })
576
+ }
577
+ }
578
+ })
579
+ })
580
+
581
+ export const AcqListMetadataSchema = z
582
+ .object({
583
+ buildPlanSnapshot: BuildPlanSnapshotSchema.optional()
584
+ })
585
+ .catchall(z.unknown())
586
+
587
+ export const ProspectingBuildTemplateIdSchema = z.string().trim().min(1).max(100).refine(isProspectingBuildTemplateId, {
588
+ message: 'buildTemplateId must match a known prospecting build template'
589
+ })
590
+
591
+ // ---------------------------------------------------------------------------
592
+ // List telemetry / progress schemas
593
+ // ---------------------------------------------------------------------------
594
+
595
+ export const ListStageCountsSchema = z.object({
596
+ // Attempted counts by canonical lead-gen stage. The detailed status
597
+ // distribution lives on ListProgress; telemetry keeps the overview payload small.
598
+ stageCounts: z.object({
599
+ populated: z.number().int(),
600
+ extracted: z.number().int(),
601
+ qualified: z.number().int(),
602
+ discovered: z.number().int(),
603
+ verified: z.number().int(),
604
+ personalized: z.number().int(),
605
+ uploaded: z.number().int()
606
+ }),
607
+ deliverability: z.object({
608
+ valid: z.number().int(),
609
+ risky: z.number().int(),
610
+ invalid: z.number().int(),
611
+ unknown: z.number().int(),
612
+ bounced: z.number().int()
613
+ })
614
+ })
615
+
616
+ export const ListTelemetrySchema = z.object({
617
+ listId: UuidSchema,
618
+ totalCompanies: z.number().int(),
619
+ totalContacts: z.number().int(),
620
+ stageCounts: ListStageCountsSchema.shape.stageCounts,
621
+ deliverability: ListStageCountsSchema.shape.deliverability,
622
+ activeWorkflows: z.array(z.string()).optional()
623
+ })
624
+
625
+ // ---------------------------------------------------------------------------
626
+ // Params
627
+ // ---------------------------------------------------------------------------
628
+
629
+ export const ListIdParamsSchema = z.object({
630
+ listId: UuidSchema
631
+ })
632
+
633
+ // ---------------------------------------------------------------------------
634
+ // Request body schemas (all use .strict() — rejects unknown fields)
635
+ // ---------------------------------------------------------------------------
636
+
637
+ export const CreateListRequestSchema = z
638
+ .object({
639
+ name: z.string().trim().min(1).max(255),
640
+ description: z.string().trim().nullable().optional(),
641
+ status: ListStatusSchema.optional(),
642
+ buildTemplateId: ProspectingBuildTemplateIdSchema.optional(),
643
+ scrapingConfig: ScrapingConfigSchema.optional(),
644
+ icp: IcpRubricSchema.optional(),
645
+ pipelineConfig: PipelineConfigSchema.optional()
646
+ })
647
+ .strict()
648
+
649
+ export const UpdateListRequestSchema = z
650
+ .object({
651
+ name: z.string().trim().min(1).max(255).optional(),
652
+ description: z.string().trim().nullable().optional(),
653
+ batchIds: z.array(z.string()).optional(),
654
+ buildTemplateId: ProspectingBuildTemplateIdSchema.optional(),
655
+ confirmBuildTemplateChange: z.literal(true).optional()
656
+ })
657
+ .strict()
658
+ .refine(
659
+ (data) =>
660
+ data.name !== undefined ||
661
+ data.description !== undefined ||
662
+ data.batchIds !== undefined ||
663
+ data.buildTemplateId !== undefined,
664
+ {
665
+ message: 'At least one field (name, description, batchIds, or buildTemplateId) must be provided'
666
+ }
667
+ )
668
+ .refine((data) => data.buildTemplateId === undefined || data.confirmBuildTemplateChange === true, {
669
+ message: 'confirmBuildTemplateChange must be true when changing buildTemplateId',
670
+ path: ['confirmBuildTemplateChange']
671
+ })
672
+
673
+ /**
674
+ * Status-only PATCH body for `/acquisition/lists/:listId/status`.
675
+ * Replaces the previous `transitionList` flow.
676
+ */
677
+ export const UpdateListStatusRequestSchema = z
678
+ .object({
679
+ status: ListStatusSchema
680
+ })
681
+ .strict()
682
+
683
+ /**
684
+ * Partial patch for the three jsonb config columns. UI sends only the edited
685
+ * subtree; server writes the field as-is (no deep merge — each column is
686
+ * replaced atomically when present in the patch).
687
+ */
688
+ export const UpdateListConfigRequestSchema = z
689
+ .object({
690
+ scrapingConfig: ScrapingConfigSchema.partial().optional(),
691
+ icp: IcpRubricSchema.partial().optional(),
692
+ pipelineConfig: PipelineConfigSchema.partial().optional()
693
+ })
694
+ .strict()
695
+ .refine((data) => data.scrapingConfig !== undefined || data.icp !== undefined || data.pipelineConfig !== undefined, {
696
+ message: 'At least one of scrapingConfig, icp, or pipelineConfig must be provided'
697
+ })
698
+
699
+ export const AddCompaniesToListRequestSchema = z
700
+ .object({
701
+ companyIds: z.array(UuidSchema).min(1).max(1000)
702
+ })
703
+ .strict()
704
+
705
+ export const RemoveCompaniesFromListRequestSchema = z
706
+ .object({
707
+ companyIds: z.array(UuidSchema).min(1).max(1000)
708
+ })
709
+ .strict()
710
+
711
+ export const AddContactsToListRequestSchema = z
712
+ .object({
713
+ contactIds: z.array(UuidSchema).min(1).max(1000)
714
+ })
715
+ .strict()
716
+
717
+ export const RecordListExecutionRequestSchema = z
718
+ .object({
719
+ executionId: UuidSchema,
720
+ configSnapshot: z.record(z.string(), z.unknown()).optional()
721
+ })
722
+ .strict()
723
+
724
+ // ---------------------------------------------------------------------------
725
+ // Response schemas (no .strict() — allows forward-compatible additions)
726
+ // ---------------------------------------------------------------------------
727
+
728
+ /**
729
+ * Single list as returned by /api/acquisition/lists/:id etc.
730
+ * Camel-cased domain shape matching AcqList in types.ts.
731
+ */
732
+ export const AcqListResponseSchema = z.object({
733
+ id: z.string(),
734
+ organizationId: z.string(),
735
+ name: z.string(),
736
+ description: z.string().nullable(),
737
+ batchIds: z.array(z.string()),
738
+ instantlyCampaignId: z.string().nullable(),
739
+ /** Lifecycle status (draft | enriching | launched | closing | archived). */
740
+ status: ListStatusSchema,
741
+ metadata: AcqListMetadataSchema,
742
+ launchedAt: z.string().nullable(),
743
+ completedAt: z.string().nullable(),
744
+ createdAt: z.string(),
745
+ /** Scraping criteria stored as jsonb on the row. */
746
+ scrapingConfig: ScrapingConfigSchema,
747
+ /** ICP / qualification rubric stored as jsonb on the row. */
748
+ icp: IcpRubricSchema,
749
+ /** Pipeline presentation contract stored as jsonb on the row. */
750
+ pipelineConfig: PipelineConfigSchema
751
+ })
752
+
753
+ export const AcqListListResponseSchema = z.array(AcqListResponseSchema)
754
+
755
+ export const ListTelemetryResponseSchema = ListTelemetrySchema
756
+
757
+ export const ListTelemetryListResponseSchema = z.array(ListTelemetrySchema)
758
+
759
+ const QueryBooleanSchema = z.preprocess((value) => {
760
+ if (value === 'true' || value === '1' || value === true) return true
761
+ if (value === 'false' || value === '0' || value === false) return false
762
+ return value
763
+ }, z.boolean())
764
+
765
+ export const ListReadQuerySchema = z
766
+ .object({
767
+ status: ListStatusSchema.optional(),
768
+ batch: z.string().trim().min(1).max(255).optional(),
769
+ vertical: z.string().trim().min(1).max(255).optional(),
770
+ limit: z.coerce.number().int().min(1).max(500).optional(),
771
+ offset: z.coerce.number().int().min(0).optional()
772
+ })
773
+ .strict()
774
+
775
+ export const GetListQuerySchema = z
776
+ .object({
777
+ includeDeals: QueryBooleanSchema.default(true),
778
+ includeProgress: QueryBooleanSchema.default(false),
779
+ dealLimit: z.coerce.number().int().min(0).max(100).default(25)
780
+ })
781
+ .strict()
782
+
783
+ /**
784
+ * Per-stage progress aggregate for a single pipeline stage.
785
+ * `attempted` counts terminal statuses, including success, no-result, skipped,
786
+ * error, and tolerant-reader `other` values.
787
+ * `total` = total member/company count for the list.
788
+ */
789
+ export const ListStageProgressSchema = z.object({
790
+ total: z.number().int().min(0),
791
+ attempted: z.number().int().min(0),
792
+ success: z.number().int().min(0),
793
+ noResult: z.number().int().min(0),
794
+ skipped: z.number().int().min(0),
795
+ error: z.number().int().min(0),
796
+ other: z.number().int().min(0),
797
+ notAttempted: z.number().int().min(0)
798
+ })
799
+
800
+ /**
801
+ * Progress response for GET /acquisition/lists/:listId/progress.
802
+ * Aggregated on-demand from processing_state status values.
803
+ * Stage keys are discovered from observed processing_state keys.
804
+ */
805
+ export const ListProgressResponseSchema = z.object({
806
+ totalMembers: z.number().int().min(0),
807
+ totalCompanies: z.number().int().min(0),
808
+ byCompanyStage: z.record(z.string(), ListStageProgressSchema),
809
+ byContactStage: z.record(z.string(), ListStageProgressSchema)
810
+ })
811
+
812
+ export const AcqListDealRefSchema = z.object({
813
+ id: z.string(),
814
+ contactEmail: z.string(),
815
+ stageKey: z.string().nullable(),
816
+ stateKey: z.string().nullable(),
817
+ sourceType: z.string().nullable(),
818
+ lastActivityAt: z.string()
819
+ })
820
+
821
+ export const AcqListLineageSchema = z.object({
822
+ deals: z.object({
823
+ total: z.number().int().min(0),
824
+ refs: z.array(AcqListDealRefSchema),
825
+ truncated: z.boolean()
826
+ })
827
+ })
828
+
829
+ export const AcqListDetailResponseSchema = AcqListResponseSchema.extend({
830
+ lineage: AcqListLineageSchema.optional(),
831
+ progress: ListProgressResponseSchema.optional()
832
+ })
833
+
834
+ export const AcqListStatusListItemSchema = z.object({
835
+ listId: z.string(),
836
+ name: z.string(),
837
+ status: ListStatusSchema,
838
+ totalCompanies: z.number().int().min(0),
839
+ totalContacts: z.number().int().min(0),
840
+ totalDeals: z.number().int().min(0),
841
+ createdAt: z.string()
842
+ })
843
+
844
+ export const AcqListStatusResponseSchema = z.object({
845
+ totalLists: z.number().int().min(0),
846
+ totalCompanies: z.number().int().min(0),
847
+ totalContacts: z.number().int().min(0),
848
+ totalDeals: z.number().int().min(0),
849
+ byStatus: z.record(z.string(), z.number().int().min(0)),
850
+ lists: z.array(AcqListStatusListItemSchema)
851
+ })
852
+
853
+ /**
854
+ * Row from acq_list_executions joined with the execution summary,
855
+ * shaped for the /lists/:id/executions response.
856
+ */
857
+ export const ListExecutionSummarySchema = z.object({
858
+ executionId: z.string(),
859
+ resourceId: z.string(),
860
+ status: z.string(),
861
+ createdAt: z.string(),
862
+ completedAt: z.string().nullable(),
863
+ durationMs: z.number().int().nullable(),
864
+ input: z.unknown().nullable().optional()
865
+ })
866
+
867
+ export const ListExecutionsResponseSchema = z.array(ListExecutionSummarySchema)
868
+
869
+ // ---------------------------------------------------------------------------
870
+ // Company / Contact API Schemas
871
+ // ---------------------------------------------------------------------------
872
+
873
+ export const AcqCompanyStatusSchema = z.enum(['active', 'invalid'])
874
+ export const AcqContactStatusSchema = z.enum(['active', 'invalid'])
875
+ export const AcqEmailValidSchema = z.enum(['VALID', 'INVALID', 'RISKY', 'UNKNOWN'])
876
+
877
+ export const CompanyIdParamsSchema = z.object({
878
+ companyId: UuidSchema
879
+ })
880
+
881
+ export const ContactIdParamsSchema = z.object({
882
+ contactId: UuidSchema
883
+ })
884
+
885
+ export const ListCompaniesQuerySchema = z
886
+ .object({
887
+ search: z.string().trim().min(1).max(200).optional(),
888
+ listId: UuidSchema.optional(),
889
+ domain: z.string().trim().min(1).max(255).optional(),
890
+ website: z.string().trim().min(1).max(2048).optional(),
891
+ segment: z.string().trim().min(1).max(255).optional(),
892
+ category: z.string().trim().min(1).max(255).optional(),
893
+ pipelineStatus: z.unknown().optional(),
894
+ batchId: z.string().trim().min(1).max(255).optional(),
895
+ status: AcqCompanyStatusSchema.optional(),
896
+ includeAll: QueryBooleanSchema.optional(),
897
+ limit: z.coerce.number().int().min(1).max(5000).default(50),
898
+ offset: z.coerce.number().int().min(0).default(0)
899
+ })
900
+ .strict()
901
+
902
+ export const ListContactsQuerySchema = z
903
+ .object({
904
+ search: z.string().trim().min(1).max(200).optional(),
905
+ listId: UuidSchema.optional(),
906
+ openingLineIsNull: QueryBooleanSchema.optional(),
907
+ batchId: z.string().trim().min(1).max(255).optional(),
908
+ contactStatus: AcqContactStatusSchema.optional(),
909
+ limit: z.coerce.number().int().min(1).max(5000).default(5000),
910
+ offset: z.coerce.number().int().min(0).default(0)
911
+ })
912
+ .strict()
913
+
914
+ export const CreateCompanyRequestSchema = z
915
+ .object({
916
+ name: z.string().trim().min(1).max(255),
917
+ clientId: UuidSchema.nullable().optional(),
918
+ domain: z.string().trim().min(1).max(255).optional(),
919
+ linkedinUrl: z.string().trim().url().optional(),
920
+ website: z.string().trim().url().optional(),
921
+ numEmployees: z.number().int().min(0).optional(),
922
+ foundedYear: z.number().int().optional(),
923
+ locationCity: z.string().trim().min(1).max(255).optional(),
924
+ locationState: z.string().trim().min(1).max(255).optional(),
925
+ category: z.string().trim().min(1).max(255).optional(),
926
+ source: z.string().trim().min(1).max(255).optional(),
927
+ batchId: z.string().trim().min(1).max(255).optional(),
928
+ pipelineStatus: z.unknown().optional(),
929
+ verticalResearch: z.string().trim().min(1).max(5000).optional()
930
+ })
931
+ .strict()
932
+
933
+ export const UpdateCompanyRequestSchema = z
934
+ .object({
935
+ name: z.string().trim().min(1).max(255).optional(),
936
+ clientId: UuidSchema.nullable().optional(),
937
+ domain: z.string().trim().min(1).max(255).optional(),
938
+ linkedinUrl: z.string().trim().url().optional(),
939
+ website: z.string().trim().url().optional(),
940
+ numEmployees: z.number().int().min(0).optional(),
941
+ foundedYear: z.number().int().optional(),
942
+ locationCity: z.string().trim().min(1).max(255).optional(),
943
+ locationState: z.string().trim().min(1).max(255).optional(),
944
+ category: z.string().trim().min(1).max(255).optional(),
945
+ segment: z.string().trim().min(1).max(255).optional(),
946
+ processingState: CompanyProcessingStateSchema.optional(),
947
+ pipelineStatus: z.unknown().optional(),
948
+ enrichmentData: z.record(z.string(), z.unknown()).optional(),
949
+ source: z.string().trim().min(1).max(255).optional(),
950
+ batchId: z.string().trim().min(1).max(255).optional(),
951
+ status: AcqCompanyStatusSchema.optional(),
952
+ verticalResearch: z.string().trim().min(1).max(5000).nullable().optional()
953
+ })
954
+ .strict()
955
+ .refine(
956
+ (data) =>
957
+ data.name !== undefined ||
958
+ data.clientId !== undefined ||
959
+ data.domain !== undefined ||
960
+ data.linkedinUrl !== undefined ||
961
+ data.website !== undefined ||
962
+ data.numEmployees !== undefined ||
963
+ data.foundedYear !== undefined ||
964
+ data.locationCity !== undefined ||
965
+ data.locationState !== undefined ||
966
+ data.category !== undefined ||
967
+ data.segment !== undefined ||
968
+ data.processingState !== undefined ||
969
+ data.pipelineStatus !== undefined ||
970
+ data.enrichmentData !== undefined ||
971
+ data.source !== undefined ||
972
+ data.batchId !== undefined ||
973
+ data.status !== undefined ||
974
+ data.verticalResearch !== undefined,
975
+ {
976
+ message: 'At least one field must be provided'
977
+ }
978
+ )
979
+
980
+ export const CreateContactRequestSchema = z
981
+ .object({
982
+ email: z.string().trim().email(),
983
+ clientId: UuidSchema.nullable().optional(),
984
+ companyId: UuidSchema.optional(),
985
+ firstName: z.string().trim().min(1).max(255).optional(),
986
+ lastName: z.string().trim().min(1).max(255).optional(),
987
+ linkedinUrl: z.string().trim().url().optional(),
988
+ title: z.string().trim().min(1).max(255).optional(),
989
+ source: z.string().trim().min(1).max(255).optional(),
990
+ sourceId: z.string().trim().min(1).max(255).optional(),
991
+ batchId: z.string().trim().min(1).max(255).optional(),
992
+ pipelineStatus: z.unknown().optional()
993
+ })
994
+ .strict()
995
+
996
+ export const UpdateContactRequestSchema = z
997
+ .object({
998
+ companyId: UuidSchema.optional(),
999
+ clientId: UuidSchema.nullable().optional(),
1000
+ emailValid: AcqEmailValidSchema.optional(),
1001
+ firstName: z.string().trim().min(1).max(255).optional(),
1002
+ lastName: z.string().trim().min(1).max(255).optional(),
1003
+ linkedinUrl: z.string().trim().url().optional(),
1004
+ title: z.string().trim().min(1).max(255).optional(),
1005
+ headline: z.string().trim().min(1).max(5000).optional(),
1006
+ filterReason: z.string().trim().min(1).max(5000).optional(),
1007
+ openingLine: z.string().trim().min(1).max(5000).optional(),
1008
+ processingState: ContactProcessingStateSchema.optional(),
1009
+ pipelineStatus: z.unknown().optional(),
1010
+ enrichmentData: z.record(z.string(), z.unknown()).optional(),
1011
+ status: AcqContactStatusSchema.optional()
1012
+ })
1013
+ .strict()
1014
+ .refine(
1015
+ (data) =>
1016
+ data.companyId !== undefined ||
1017
+ data.clientId !== undefined ||
1018
+ data.emailValid !== undefined ||
1019
+ data.firstName !== undefined ||
1020
+ data.lastName !== undefined ||
1021
+ data.linkedinUrl !== undefined ||
1022
+ data.title !== undefined ||
1023
+ data.headline !== undefined ||
1024
+ data.filterReason !== undefined ||
1025
+ data.openingLine !== undefined ||
1026
+ data.processingState !== undefined ||
1027
+ data.pipelineStatus !== undefined ||
1028
+ data.enrichmentData !== undefined ||
1029
+ data.status !== undefined,
1030
+ {
1031
+ message: 'At least one field must be provided'
1032
+ }
1033
+ )
1034
+
1035
+ export const AcqCompanyResponseSchema = z.object({
1036
+ id: z.string(),
1037
+ organizationId: z.string(),
1038
+ clientId: z.string().nullable().optional(),
1039
+ name: z.string(),
1040
+ domain: z.string().nullable(),
1041
+ linkedinUrl: z.string().nullable(),
1042
+ website: z.string().nullable(),
1043
+ numEmployees: z.number().nullable(),
1044
+ foundedYear: z.number().nullable(),
1045
+ locationCity: z.string().nullable(),
1046
+ locationState: z.string().nullable(),
1047
+ category: z.string().nullable(),
1048
+ categoryPain: z.string().nullable(),
1049
+ segment: z.string().nullable(),
1050
+ processingState: CompanyProcessingStateSchema.nullable(),
1051
+ pipelineStatus: z.unknown().nullable().optional(),
1052
+ enrichmentData: z.record(z.string(), z.unknown()).nullable(),
1053
+ source: z.string().nullable(),
1054
+ batchId: z.string().nullable(),
1055
+ status: AcqCompanyStatusSchema,
1056
+ contactCount: z.number().int().min(0),
1057
+ verticalResearch: z.string().nullable(),
1058
+ createdAt: z.string(),
1059
+ updatedAt: z.string()
1060
+ })
1061
+
1062
+ export const AcqCompanyListResponseSchema = z.object({
1063
+ data: z.array(AcqCompanyResponseSchema),
1064
+ total: z.number().int(),
1065
+ limit: z.number().int(),
1066
+ offset: z.number().int()
1067
+ })
1068
+
1069
+ export const AcqCompanyFacetsResponseSchema = z.object({
1070
+ segments: z.array(z.string()),
1071
+ categories: z.array(z.string()),
1072
+ statuses: z.array(AcqCompanyStatusSchema)
1073
+ })
1074
+
1075
+ export const AcqContactCompanySummarySchema = z.object({
1076
+ id: z.string(),
1077
+ name: z.string(),
1078
+ domain: z.string().nullable(),
1079
+ website: z.string().nullable(),
1080
+ linkedinUrl: z.string().nullable(),
1081
+ segment: z.string().nullable(),
1082
+ category: z.string().nullable(),
1083
+ status: AcqCompanyStatusSchema
1084
+ })
1085
+
1086
+ export const AcqContactResponseSchema = z.object({
1087
+ id: z.string(),
1088
+ organizationId: z.string(),
1089
+ clientId: z.string().nullable().optional(),
1090
+ companyId: z.string().nullable(),
1091
+ email: z.string(),
1092
+ emailValid: AcqEmailValidSchema.nullable(),
1093
+ firstName: z.string().nullable(),
1094
+ lastName: z.string().nullable(),
1095
+ linkedinUrl: z.string().nullable(),
1096
+ title: z.string().nullable(),
1097
+ headline: z.string().nullable(),
1098
+ filterReason: z.string().nullable(),
1099
+ openingLine: z.string().nullable(),
1100
+ source: z.string().nullable(),
1101
+ sourceId: z.string().nullable(),
1102
+ processingState: ContactProcessingStateSchema.nullable(),
1103
+ pipelineStatus: z.unknown().nullable().optional(),
1104
+ enrichmentData: z.record(z.string(), z.unknown()).nullable(),
1105
+ attioPersonId: z.string().nullable(),
1106
+ batchId: z.string().nullable(),
1107
+ status: AcqContactStatusSchema,
1108
+ company: AcqContactCompanySummarySchema.nullable().optional(),
1109
+ createdAt: z.string(),
1110
+ updatedAt: z.string()
1111
+ })
1112
+
1113
+ export const AcqContactListResponseSchema = z.object({
1114
+ data: z.array(AcqContactResponseSchema),
1115
+ total: z.number().int(),
1116
+ limit: z.number().int(),
1117
+ offset: z.number().int()
1118
+ })
1119
+
1120
+ // ---------------------------------------------------------------------------
1121
+ // Track A: Artifacts API Schemas
1122
+ // ---------------------------------------------------------------------------
1123
+
1124
+ export const AcqArtifactOwnerKindSchema = z.enum(['company', 'contact', 'deal', 'list', 'list_member'])
1125
+
1126
+ export const ListArtifactsQuerySchema = z
1127
+ .object({
1128
+ ownerKind: AcqArtifactOwnerKindSchema,
1129
+ ownerId: UuidSchema
1130
+ })
1131
+ .strict()
1132
+
1133
+ export const CreateArtifactRequestSchema = z
1134
+ .object({
1135
+ ownerKind: AcqArtifactOwnerKindSchema,
1136
+ ownerId: UuidSchema,
1137
+ kind: z.string().trim().min(1).max(255),
1138
+ content: z.record(z.string(), z.unknown()),
1139
+ sourceExecutionId: UuidSchema.optional()
1140
+ })
1141
+ .strict()
1142
+
1143
+ export const AcqArtifactResponseSchema = z.object({
1144
+ id: z.string(),
1145
+ organizationId: z.string(),
1146
+ ownerKind: z.string(),
1147
+ ownerId: z.string(),
1148
+ kind: z.string(),
1149
+ content: z.record(z.string(), z.unknown()),
1150
+ sourceExecutionId: z.string().nullable(),
1151
+ createdBy: z.string().nullable(),
1152
+ createdAt: z.string(),
1153
+ version: z.number().int()
1154
+ })
1155
+
1156
+ export const AcqArtifactListResponseSchema = z.object({
1157
+ artifacts: z.array(AcqArtifactResponseSchema)
1158
+ })
1159
+
1160
+ // ---------------------------------------------------------------------------
1161
+ // Track B: List Members API Schemas
1162
+ // ---------------------------------------------------------------------------
1163
+
1164
+ export const ListMembersQuerySchema = z
1165
+ .object({
1166
+ limit: z.coerce.number().int().min(1).max(500).default(50),
1167
+ offset: z.coerce.number().int().min(0).default(0)
1168
+ })
1169
+ .strict()
1170
+
1171
+ export const ListRecordEntitySchema = z.enum(['company', 'contact'])
1172
+
1176
1173
  export const ListRecordsQuerySchema = z
1177
1174
  .object({
1178
1175
  entity: ListRecordEntitySchema,
@@ -1181,315 +1178,315 @@ export const ListRecordsQuerySchema = z
1181
1178
  offset: z.coerce.number().int().min(0).default(0)
1182
1179
  })
1183
1180
  .strict()
1184
-
1185
- export const MemberIdParamsSchema = z.object({
1186
- memberId: UuidSchema
1187
- })
1188
-
1189
- export const AcqListMemberContactSummarySchema = z.object({
1190
- id: z.string(),
1191
- email: z.string(),
1192
- firstName: z.string().nullable(),
1193
- lastName: z.string().nullable(),
1194
- title: z.string().nullable(),
1195
- linkedinUrl: z.string().nullable(),
1196
- companyId: z.string().nullable()
1197
- })
1198
-
1199
- export const AcqListMemberResponseSchema = z.object({
1200
- id: z.string(),
1201
- listId: z.string(),
1202
- contactId: z.string(),
1203
- pipelineKey: z.string(),
1204
- stageKey: z.string(),
1205
- stateKey: z.string(),
1206
- activityLog: z.unknown(),
1207
- addedAt: z.string(),
1208
- addedBy: z.string().nullable(),
1209
- sourceExecutionId: z.string().nullable(),
1210
- contact: AcqListMemberContactSummarySchema.nullable()
1211
- })
1212
-
1213
- export const AcqListMembersResponseSchema = z.object({
1214
- members: z.array(AcqListMemberResponseSchema)
1215
- })
1216
-
1217
- export const AcqListRecordCompanySummarySchema = z.object({
1218
- id: z.string(),
1219
- name: z.string(),
1220
- domain: z.string().nullable(),
1221
- website: z.string().nullable(),
1222
- linkedinUrl: z.string().nullable(),
1223
- numEmployees: z.number().nullable(),
1224
- foundedYear: z.number().nullable(),
1225
- locationCity: z.string().nullable(),
1226
- locationState: z.string().nullable(),
1227
- category: z.string().nullable(),
1228
- segment: z.string().nullable(),
1229
- status: AcqCompanyStatusSchema,
1230
- qualificationScore: z.number().nullable(),
1231
- qualificationSignals: z.record(z.string(), z.unknown()).nullable(),
1232
- qualificationRubricKey: z.string().nullable()
1233
- })
1234
-
1235
- export const AcqListRecordContactSummarySchema = z.object({
1236
- id: z.string(),
1237
- email: z.string(),
1238
- firstName: z.string().nullable(),
1239
- lastName: z.string().nullable(),
1240
- title: z.string().nullable(),
1241
- headline: z.string().nullable(),
1242
- linkedinUrl: z.string().nullable(),
1243
- companyId: z.string().nullable(),
1244
- status: AcqContactStatusSchema,
1245
- qualificationScore: z.number().nullable(),
1246
- qualificationSignals: z.record(z.string(), z.unknown()).nullable(),
1247
- qualificationRubricKey: z.string().nullable()
1248
- })
1249
-
1250
- const ListRecordBaseSchema = z.object({
1251
- id: z.string(),
1252
- listId: z.string(),
1253
- pipelineKey: z.string(),
1254
- stageKey: z.string(),
1255
- stateKey: z.string(),
1256
- activityLog: z.unknown(),
1257
- addedAt: z.string(),
1258
- addedBy: z.string().nullable(),
1259
- sourceExecutionId: z.string().nullable(),
1260
- processingState: z.record(z.string(), z.unknown()).nullable(),
1261
- enrichmentData: z.record(z.string(), z.unknown()).nullable()
1262
- })
1263
-
1264
- export const AcqListCompanyRecordRowSchema = ListRecordBaseSchema.extend({
1265
- entity: z.literal('company'),
1266
- companyId: z.string(),
1267
- company: AcqListRecordCompanySummarySchema.nullable()
1268
- })
1269
-
1270
- export const AcqListContactRecordRowSchema = ListRecordBaseSchema.extend({
1271
- entity: z.literal('contact'),
1272
- contactId: z.string(),
1273
- contact: AcqListRecordContactSummarySchema.nullable()
1274
- })
1275
-
1276
- export const ListRecordRowSchema = z.discriminatedUnion('entity', [
1277
- AcqListCompanyRecordRowSchema,
1278
- AcqListContactRecordRowSchema
1279
- ])
1280
-
1281
- export const ListRecordsResponseSchema = z.object({
1282
- data: z.array(ListRecordRowSchema),
1283
- total: z.number().int().min(0),
1284
- limit: z.number().int().min(1),
1285
- offset: z.number().int().min(0)
1286
- })
1287
-
1288
- // ---------------------------------------------------------------------------
1289
- // Track B: List Companies API Schemas
1290
- // ---------------------------------------------------------------------------
1291
-
1292
- export const ListCompanyIdParamsSchema = z.object({
1293
- listCompanyId: UuidSchema
1294
- })
1295
-
1296
- export const AcqListCompanyResponseSchema = z.object({
1297
- id: z.string(),
1298
- listId: z.string(),
1299
- companyId: z.string(),
1300
- pipelineKey: z.string(),
1301
- stageKey: z.string(),
1302
- stateKey: z.string(),
1303
- activityLog: z.unknown(),
1304
- addedAt: z.string(),
1305
- addedBy: z.string().nullable(),
1306
- sourceExecutionId: z.string().nullable()
1307
- })
1308
-
1309
- // ---------------------------------------------------------------------------
1310
- // Track B: Transition request for list, list-member, and list-company substrate routes.
1311
- // CRM deals use DealSchemas.TransitionItemRequest, which is catalog-backed.
1312
- // ---------------------------------------------------------------------------
1313
-
1314
- export const AcqCompanySchemas = {
1315
- CompanyProcessingState: CompanyProcessingStateSchema,
1316
- CompanyIdParams: CompanyIdParamsSchema,
1317
- ListCompaniesQuery: ListCompaniesQuerySchema,
1318
- CreateCompanyRequest: CreateCompanyRequestSchema,
1319
- UpdateCompanyRequest: UpdateCompanyRequestSchema,
1320
- AcqCompanyResponse: AcqCompanyResponseSchema,
1321
- AcqCompanyListResponse: AcqCompanyListResponseSchema,
1322
- AcqCompanyFacetsResponse: AcqCompanyFacetsResponseSchema
1323
- }
1324
-
1325
- export const AcqContactSchemas = {
1326
- ContactProcessingState: ContactProcessingStateSchema,
1327
- ContactIdParams: ContactIdParamsSchema,
1328
- ListContactsQuery: ListContactsQuerySchema,
1329
- CreateContactRequest: CreateContactRequestSchema,
1330
- UpdateContactRequest: UpdateContactRequestSchema,
1331
- AcqContactResponse: AcqContactResponseSchema,
1332
- AcqContactListResponse: AcqContactListResponseSchema
1333
- }
1334
-
1335
- export type AcqCompanyStatus = z.infer<typeof AcqCompanyStatusSchema>
1336
- export type AcqContactStatus = z.infer<typeof AcqContactStatusSchema>
1337
- export type AcqEmailValid = z.infer<typeof AcqEmailValidSchema>
1338
- export type CompanyIdParams = z.infer<typeof CompanyIdParamsSchema>
1339
- export type ContactIdParams = z.infer<typeof ContactIdParamsSchema>
1340
- export type ListCompaniesQuery = z.infer<typeof ListCompaniesQuerySchema>
1341
- export type ListContactsQuery = z.infer<typeof ListContactsQuerySchema>
1342
- export type CreateCompanyRequest = z.infer<typeof CreateCompanyRequestSchema>
1343
- export type UpdateCompanyRequest = z.infer<typeof UpdateCompanyRequestSchema>
1344
- export type CreateContactRequest = z.infer<typeof CreateContactRequestSchema>
1345
- export type UpdateContactRequest = z.infer<typeof UpdateContactRequestSchema>
1346
- export type AcqCompanyResponse = z.infer<typeof AcqCompanyResponseSchema>
1347
- export type AcqCompanyListResponse = z.infer<typeof AcqCompanyListResponseSchema>
1348
- export type AcqCompanyFacetsResponse = z.infer<typeof AcqCompanyFacetsResponseSchema>
1349
- export type AcqContactCompanySummary = z.infer<typeof AcqContactCompanySummarySchema>
1350
- export type AcqContactResponse = z.infer<typeof AcqContactResponseSchema>
1351
- export type AcqContactListResponse = z.infer<typeof AcqContactListResponseSchema>
1352
-
1353
- // ---------------------------------------------------------------------------
1354
- // Bundled export
1355
- // ---------------------------------------------------------------------------
1356
-
1357
- export const AcqListSchemas = {
1358
- // Params
1359
- ListIdParams: ListIdParamsSchema,
1360
-
1361
- // Primitives (for UI / tests)
1362
- ListStatus: ListStatusSchema,
1363
- ScrapingConfig: ScrapingConfigSchema,
1364
- IcpRubric: IcpRubricSchema,
1365
- PipelineConfig: PipelineConfigSchema,
1366
- PipelineStage: PipelineStageSchema,
1367
- BuildPlanSnapshot: BuildPlanSnapshotSchema,
1368
- BuildPlanSnapshotStep: BuildPlanSnapshotStepSchema,
1369
- AcqListMetadata: AcqListMetadataSchema,
1370
- LeadGenStageKey: LeadGenStageKeySchema,
1371
- LeadGenActionKey: LeadGenActionKeySchema,
1372
- ProcessingStageStatus: ProcessingStageStatusSchema,
1373
- ProcessingState: ProcessingStateSchema,
1374
- ListStageCounts: ListStageCountsSchema,
1375
- ListTelemetry: ListTelemetrySchema,
1376
-
1377
- // Requests
1378
- ListReadQuery: ListReadQuerySchema,
1379
- GetListQuery: GetListQuerySchema,
1380
- CreateListRequest: CreateListRequestSchema,
1381
- UpdateListRequest: UpdateListRequestSchema,
1382
- UpdateListStatusRequest: UpdateListStatusRequestSchema,
1383
- UpdateListConfigRequest: UpdateListConfigRequestSchema,
1384
- AddCompaniesToListRequest: AddCompaniesToListRequestSchema,
1385
- RemoveCompaniesFromListRequest: RemoveCompaniesFromListRequestSchema,
1386
- AddContactsToListRequest: AddContactsToListRequestSchema,
1387
- RecordListExecutionRequest: RecordListExecutionRequestSchema,
1388
-
1389
- // Responses
1390
- AcqListResponse: AcqListResponseSchema,
1391
- AcqListDetailResponse: AcqListDetailResponseSchema,
1392
- AcqListListResponse: AcqListListResponseSchema,
1393
- AcqListDealRef: AcqListDealRefSchema,
1394
- AcqListLineage: AcqListLineageSchema,
1395
- AcqListStatusResponse: AcqListStatusResponseSchema,
1396
- ListTelemetryResponse: ListTelemetryResponseSchema,
1397
- ListTelemetryListResponse: ListTelemetryListResponseSchema,
1398
- ListExecutionsResponse: ListExecutionsResponseSchema,
1399
- ListProgressResponse: ListProgressResponseSchema
1400
- }
1401
-
1402
- // ---------------------------------------------------------------------------
1403
- // Track A/B bundled schemas
1404
- // ---------------------------------------------------------------------------
1405
-
1406
- export const AcqSubstrateSchemas = {
1407
- // Artifacts
1408
- ListArtifactsQuery: ListArtifactsQuerySchema,
1409
- CreateArtifactRequest: CreateArtifactRequestSchema,
1410
- AcqArtifactResponse: AcqArtifactResponseSchema,
1411
- AcqArtifactListResponse: AcqArtifactListResponseSchema,
1412
-
1413
- // List members
1414
- ListMembersQuery: ListMembersQuerySchema,
1415
- ListRecordsQuery: ListRecordsQuerySchema,
1416
- MemberIdParams: MemberIdParamsSchema,
1417
- AcqListMemberResponse: AcqListMemberResponseSchema,
1418
- AcqListMembersResponse: AcqListMembersResponseSchema,
1419
- AcqListCompanyRecordRow: AcqListCompanyRecordRowSchema,
1420
- AcqListContactRecordRow: AcqListContactRecordRowSchema,
1421
- ListRecordRow: ListRecordRowSchema,
1422
- ListRecordsResponse: ListRecordsResponseSchema,
1423
-
1424
- // List companies
1425
- ListCompanyIdParams: ListCompanyIdParamsSchema,
1426
- AcqListCompanyResponse: AcqListCompanyResponseSchema,
1427
-
1428
- // Transition (generic stateful substrate)
1429
- TransitionItemRequest: TransitionItemRequestSchema
1430
- }
1431
-
1432
- // ---------------------------------------------------------------------------
1433
- // Inferred types
1434
- // ---------------------------------------------------------------------------
1435
-
1436
- // ---------------------------------------------------------------------------
1437
- // Inferred types — Track A/B substrate
1438
- // ---------------------------------------------------------------------------
1439
-
1440
- export type AcqArtifactOwnerKind = z.infer<typeof AcqArtifactOwnerKindSchema>
1441
- export type ListArtifactsQuery = z.infer<typeof ListArtifactsQuerySchema>
1442
- export type CreateArtifactRequest = z.infer<typeof CreateArtifactRequestSchema>
1443
- export type AcqArtifactResponse = z.infer<typeof AcqArtifactResponseSchema>
1444
- export type AcqArtifactListResponse = z.infer<typeof AcqArtifactListResponseSchema>
1445
- export type ListMembersQuery = z.infer<typeof ListMembersQuerySchema>
1446
- export type ListRecordEntity = z.infer<typeof ListRecordEntitySchema>
1447
- export type ListRecordsQuery = z.infer<typeof ListRecordsQuerySchema>
1448
- export type MemberIdParams = z.infer<typeof MemberIdParamsSchema>
1449
- export type AcqListMemberContactSummary = z.infer<typeof AcqListMemberContactSummarySchema>
1450
- export type AcqListMemberResponse = z.infer<typeof AcqListMemberResponseSchema>
1451
- export type AcqListMembersResponse = z.infer<typeof AcqListMembersResponseSchema>
1452
- export type AcqListCompanyRecordRow = z.infer<typeof AcqListCompanyRecordRowSchema>
1453
- export type AcqListContactRecordRow = z.infer<typeof AcqListContactRecordRowSchema>
1454
- export type ListRecordRow = z.infer<typeof ListRecordRowSchema>
1455
- export type ListRecordsResponse = z.infer<typeof ListRecordsResponseSchema>
1456
- export type ListCompanyIdParams = z.infer<typeof ListCompanyIdParamsSchema>
1457
- export type AcqListCompanyResponse = z.infer<typeof AcqListCompanyResponseSchema>
1458
-
1459
- // ---------------------------------------------------------------------------
1460
-
1461
- export type ListStatus = z.infer<typeof ListStatusSchema>
1462
- export type ScrapingConfig = z.infer<typeof ScrapingConfigSchema>
1463
- export type IcpRubric = z.infer<typeof IcpRubricSchema>
1464
- export type PipelineStage = z.infer<typeof PipelineStageSchema>
1465
- export type PipelineConfig = z.infer<typeof PipelineConfigSchema>
1466
- export type BuildPlanSnapshotStep = z.infer<typeof BuildPlanSnapshotStepSchema>
1467
- export type BuildPlanSnapshot = z.infer<typeof BuildPlanSnapshotSchema>
1468
- export type AcqListMetadata = z.infer<typeof AcqListMetadataSchema>
1469
- export type LeadGenActionKey = z.infer<typeof LeadGenActionKeySchema>
1470
- export type ProcessingStageStatus = z.infer<typeof ProcessingStageStatusSchema>
1471
- export type ListStageCountsInput = z.infer<typeof ListStageCountsSchema>['stageCounts']
1472
- export type ListTelemetryInput = z.infer<typeof ListTelemetrySchema>
1473
- export type ListIdParams = z.infer<typeof ListIdParamsSchema>
1474
- export type ListReadQuery = z.infer<typeof ListReadQuerySchema>
1475
- export type GetListQuery = z.infer<typeof GetListQuerySchema>
1476
- export type CreateListRequest = z.infer<typeof CreateListRequestSchema>
1477
- export type UpdateListRequest = z.infer<typeof UpdateListRequestSchema>
1478
- export type UpdateListStatusRequest = z.infer<typeof UpdateListStatusRequestSchema>
1479
- export type UpdateListConfigRequest = z.infer<typeof UpdateListConfigRequestSchema>
1480
- export type AddCompaniesToListRequest = z.infer<typeof AddCompaniesToListRequestSchema>
1481
- export type RemoveCompaniesFromListRequest = z.infer<typeof RemoveCompaniesFromListRequestSchema>
1482
- export type AddContactsToListRequest = z.infer<typeof AddContactsToListRequestSchema>
1483
- export type RecordListExecutionRequest = z.infer<typeof RecordListExecutionRequestSchema>
1484
- export type AcqListResponse = z.infer<typeof AcqListResponseSchema>
1485
- export type AcqListDetailResponse = z.infer<typeof AcqListDetailResponseSchema>
1486
- export type AcqListListResponse = z.infer<typeof AcqListListResponseSchema>
1487
- export type AcqListDealRef = z.infer<typeof AcqListDealRefSchema>
1488
- export type AcqListLineage = z.infer<typeof AcqListLineageSchema>
1489
- export type AcqListStatusResponse = z.infer<typeof AcqListStatusResponseSchema>
1490
- export type ListTelemetryResponse = z.infer<typeof ListTelemetryResponseSchema>
1491
- export type ListTelemetryListResponse = z.infer<typeof ListTelemetryListResponseSchema>
1492
- export type ListExecutionSummaryInput = z.infer<typeof ListExecutionSummarySchema>
1493
- export type ListExecutionsResponse = z.infer<typeof ListExecutionsResponseSchema>
1494
- export type ListStageProgress = z.infer<typeof ListStageProgressSchema>
1495
- export type ListProgress = z.infer<typeof ListProgressResponseSchema>
1181
+
1182
+ export const MemberIdParamsSchema = z.object({
1183
+ memberId: UuidSchema
1184
+ })
1185
+
1186
+ export const AcqListMemberContactSummarySchema = z.object({
1187
+ id: z.string(),
1188
+ email: z.string(),
1189
+ firstName: z.string().nullable(),
1190
+ lastName: z.string().nullable(),
1191
+ title: z.string().nullable(),
1192
+ linkedinUrl: z.string().nullable(),
1193
+ companyId: z.string().nullable()
1194
+ })
1195
+
1196
+ export const AcqListMemberResponseSchema = z.object({
1197
+ id: z.string(),
1198
+ listId: z.string(),
1199
+ contactId: z.string(),
1200
+ pipelineKey: z.string(),
1201
+ stageKey: z.string(),
1202
+ stateKey: z.string(),
1203
+ activityLog: z.unknown(),
1204
+ addedAt: z.string(),
1205
+ addedBy: z.string().nullable(),
1206
+ sourceExecutionId: z.string().nullable(),
1207
+ contact: AcqListMemberContactSummarySchema.nullable()
1208
+ })
1209
+
1210
+ export const AcqListMembersResponseSchema = z.object({
1211
+ members: z.array(AcqListMemberResponseSchema)
1212
+ })
1213
+
1214
+ export const AcqListRecordCompanySummarySchema = z.object({
1215
+ id: z.string(),
1216
+ name: z.string(),
1217
+ domain: z.string().nullable(),
1218
+ website: z.string().nullable(),
1219
+ linkedinUrl: z.string().nullable(),
1220
+ numEmployees: z.number().nullable(),
1221
+ foundedYear: z.number().nullable(),
1222
+ locationCity: z.string().nullable(),
1223
+ locationState: z.string().nullable(),
1224
+ category: z.string().nullable(),
1225
+ segment: z.string().nullable(),
1226
+ status: AcqCompanyStatusSchema,
1227
+ qualificationScore: z.number().nullable(),
1228
+ qualificationSignals: z.record(z.string(), z.unknown()).nullable(),
1229
+ qualificationRubricKey: z.string().nullable()
1230
+ })
1231
+
1232
+ export const AcqListRecordContactSummarySchema = z.object({
1233
+ id: z.string(),
1234
+ email: z.string(),
1235
+ firstName: z.string().nullable(),
1236
+ lastName: z.string().nullable(),
1237
+ title: z.string().nullable(),
1238
+ headline: z.string().nullable(),
1239
+ linkedinUrl: z.string().nullable(),
1240
+ companyId: z.string().nullable(),
1241
+ status: AcqContactStatusSchema,
1242
+ qualificationScore: z.number().nullable(),
1243
+ qualificationSignals: z.record(z.string(), z.unknown()).nullable(),
1244
+ qualificationRubricKey: z.string().nullable()
1245
+ })
1246
+
1247
+ const ListRecordBaseSchema = z.object({
1248
+ id: z.string(),
1249
+ listId: z.string(),
1250
+ pipelineKey: z.string(),
1251
+ stageKey: z.string(),
1252
+ stateKey: z.string(),
1253
+ activityLog: z.unknown(),
1254
+ addedAt: z.string(),
1255
+ addedBy: z.string().nullable(),
1256
+ sourceExecutionId: z.string().nullable(),
1257
+ processingState: z.record(z.string(), z.unknown()).nullable(),
1258
+ enrichmentData: z.record(z.string(), z.unknown()).nullable()
1259
+ })
1260
+
1261
+ export const AcqListCompanyRecordRowSchema = ListRecordBaseSchema.extend({
1262
+ entity: z.literal('company'),
1263
+ companyId: z.string(),
1264
+ company: AcqListRecordCompanySummarySchema.nullable()
1265
+ })
1266
+
1267
+ export const AcqListContactRecordRowSchema = ListRecordBaseSchema.extend({
1268
+ entity: z.literal('contact'),
1269
+ contactId: z.string(),
1270
+ contact: AcqListRecordContactSummarySchema.nullable()
1271
+ })
1272
+
1273
+ export const ListRecordRowSchema = z.discriminatedUnion('entity', [
1274
+ AcqListCompanyRecordRowSchema,
1275
+ AcqListContactRecordRowSchema
1276
+ ])
1277
+
1278
+ export const ListRecordsResponseSchema = z.object({
1279
+ data: z.array(ListRecordRowSchema),
1280
+ total: z.number().int().min(0),
1281
+ limit: z.number().int().min(1),
1282
+ offset: z.number().int().min(0)
1283
+ })
1284
+
1285
+ // ---------------------------------------------------------------------------
1286
+ // Track B: List Companies API Schemas
1287
+ // ---------------------------------------------------------------------------
1288
+
1289
+ export const ListCompanyIdParamsSchema = z.object({
1290
+ listCompanyId: UuidSchema
1291
+ })
1292
+
1293
+ export const AcqListCompanyResponseSchema = z.object({
1294
+ id: z.string(),
1295
+ listId: z.string(),
1296
+ companyId: z.string(),
1297
+ pipelineKey: z.string(),
1298
+ stageKey: z.string(),
1299
+ stateKey: z.string(),
1300
+ activityLog: z.unknown(),
1301
+ addedAt: z.string(),
1302
+ addedBy: z.string().nullable(),
1303
+ sourceExecutionId: z.string().nullable()
1304
+ })
1305
+
1306
+ // ---------------------------------------------------------------------------
1307
+ // Track B: Transition request for list, list-member, and list-company substrate routes.
1308
+ // CRM deals use DealSchemas.TransitionItemRequest, which is catalog-backed.
1309
+ // ---------------------------------------------------------------------------
1310
+
1311
+ export const AcqCompanySchemas = {
1312
+ CompanyProcessingState: CompanyProcessingStateSchema,
1313
+ CompanyIdParams: CompanyIdParamsSchema,
1314
+ ListCompaniesQuery: ListCompaniesQuerySchema,
1315
+ CreateCompanyRequest: CreateCompanyRequestSchema,
1316
+ UpdateCompanyRequest: UpdateCompanyRequestSchema,
1317
+ AcqCompanyResponse: AcqCompanyResponseSchema,
1318
+ AcqCompanyListResponse: AcqCompanyListResponseSchema,
1319
+ AcqCompanyFacetsResponse: AcqCompanyFacetsResponseSchema
1320
+ }
1321
+
1322
+ export const AcqContactSchemas = {
1323
+ ContactProcessingState: ContactProcessingStateSchema,
1324
+ ContactIdParams: ContactIdParamsSchema,
1325
+ ListContactsQuery: ListContactsQuerySchema,
1326
+ CreateContactRequest: CreateContactRequestSchema,
1327
+ UpdateContactRequest: UpdateContactRequestSchema,
1328
+ AcqContactResponse: AcqContactResponseSchema,
1329
+ AcqContactListResponse: AcqContactListResponseSchema
1330
+ }
1331
+
1332
+ export type AcqCompanyStatus = z.infer<typeof AcqCompanyStatusSchema>
1333
+ export type AcqContactStatus = z.infer<typeof AcqContactStatusSchema>
1334
+ export type AcqEmailValid = z.infer<typeof AcqEmailValidSchema>
1335
+ export type CompanyIdParams = z.infer<typeof CompanyIdParamsSchema>
1336
+ export type ContactIdParams = z.infer<typeof ContactIdParamsSchema>
1337
+ export type ListCompaniesQuery = z.infer<typeof ListCompaniesQuerySchema>
1338
+ export type ListContactsQuery = z.infer<typeof ListContactsQuerySchema>
1339
+ export type CreateCompanyRequest = z.infer<typeof CreateCompanyRequestSchema>
1340
+ export type UpdateCompanyRequest = z.infer<typeof UpdateCompanyRequestSchema>
1341
+ export type CreateContactRequest = z.infer<typeof CreateContactRequestSchema>
1342
+ export type UpdateContactRequest = z.infer<typeof UpdateContactRequestSchema>
1343
+ export type AcqCompanyResponse = z.infer<typeof AcqCompanyResponseSchema>
1344
+ export type AcqCompanyListResponse = z.infer<typeof AcqCompanyListResponseSchema>
1345
+ export type AcqCompanyFacetsResponse = z.infer<typeof AcqCompanyFacetsResponseSchema>
1346
+ export type AcqContactCompanySummary = z.infer<typeof AcqContactCompanySummarySchema>
1347
+ export type AcqContactResponse = z.infer<typeof AcqContactResponseSchema>
1348
+ export type AcqContactListResponse = z.infer<typeof AcqContactListResponseSchema>
1349
+
1350
+ // ---------------------------------------------------------------------------
1351
+ // Bundled export
1352
+ // ---------------------------------------------------------------------------
1353
+
1354
+ export const AcqListSchemas = {
1355
+ // Params
1356
+ ListIdParams: ListIdParamsSchema,
1357
+
1358
+ // Primitives (for UI / tests)
1359
+ ListStatus: ListStatusSchema,
1360
+ ScrapingConfig: ScrapingConfigSchema,
1361
+ IcpRubric: IcpRubricSchema,
1362
+ PipelineConfig: PipelineConfigSchema,
1363
+ PipelineStage: PipelineStageSchema,
1364
+ BuildPlanSnapshot: BuildPlanSnapshotSchema,
1365
+ BuildPlanSnapshotStep: BuildPlanSnapshotStepSchema,
1366
+ AcqListMetadata: AcqListMetadataSchema,
1367
+ LeadGenStageKey: LeadGenStageKeySchema,
1368
+ LeadGenActionKey: LeadGenActionKeySchema,
1369
+ ProcessingStageStatus: ProcessingStageStatusSchema,
1370
+ ProcessingState: ProcessingStateSchema,
1371
+ ListStageCounts: ListStageCountsSchema,
1372
+ ListTelemetry: ListTelemetrySchema,
1373
+
1374
+ // Requests
1375
+ ListReadQuery: ListReadQuerySchema,
1376
+ GetListQuery: GetListQuerySchema,
1377
+ CreateListRequest: CreateListRequestSchema,
1378
+ UpdateListRequest: UpdateListRequestSchema,
1379
+ UpdateListStatusRequest: UpdateListStatusRequestSchema,
1380
+ UpdateListConfigRequest: UpdateListConfigRequestSchema,
1381
+ AddCompaniesToListRequest: AddCompaniesToListRequestSchema,
1382
+ RemoveCompaniesFromListRequest: RemoveCompaniesFromListRequestSchema,
1383
+ AddContactsToListRequest: AddContactsToListRequestSchema,
1384
+ RecordListExecutionRequest: RecordListExecutionRequestSchema,
1385
+
1386
+ // Responses
1387
+ AcqListResponse: AcqListResponseSchema,
1388
+ AcqListDetailResponse: AcqListDetailResponseSchema,
1389
+ AcqListListResponse: AcqListListResponseSchema,
1390
+ AcqListDealRef: AcqListDealRefSchema,
1391
+ AcqListLineage: AcqListLineageSchema,
1392
+ AcqListStatusResponse: AcqListStatusResponseSchema,
1393
+ ListTelemetryResponse: ListTelemetryResponseSchema,
1394
+ ListTelemetryListResponse: ListTelemetryListResponseSchema,
1395
+ ListExecutionsResponse: ListExecutionsResponseSchema,
1396
+ ListProgressResponse: ListProgressResponseSchema
1397
+ }
1398
+
1399
+ // ---------------------------------------------------------------------------
1400
+ // Track A/B bundled schemas
1401
+ // ---------------------------------------------------------------------------
1402
+
1403
+ export const AcqSubstrateSchemas = {
1404
+ // Artifacts
1405
+ ListArtifactsQuery: ListArtifactsQuerySchema,
1406
+ CreateArtifactRequest: CreateArtifactRequestSchema,
1407
+ AcqArtifactResponse: AcqArtifactResponseSchema,
1408
+ AcqArtifactListResponse: AcqArtifactListResponseSchema,
1409
+
1410
+ // List members
1411
+ ListMembersQuery: ListMembersQuerySchema,
1412
+ ListRecordsQuery: ListRecordsQuerySchema,
1413
+ MemberIdParams: MemberIdParamsSchema,
1414
+ AcqListMemberResponse: AcqListMemberResponseSchema,
1415
+ AcqListMembersResponse: AcqListMembersResponseSchema,
1416
+ AcqListCompanyRecordRow: AcqListCompanyRecordRowSchema,
1417
+ AcqListContactRecordRow: AcqListContactRecordRowSchema,
1418
+ ListRecordRow: ListRecordRowSchema,
1419
+ ListRecordsResponse: ListRecordsResponseSchema,
1420
+
1421
+ // List companies
1422
+ ListCompanyIdParams: ListCompanyIdParamsSchema,
1423
+ AcqListCompanyResponse: AcqListCompanyResponseSchema,
1424
+
1425
+ // Transition (generic stateful substrate)
1426
+ TransitionItemRequest: TransitionItemRequestSchema
1427
+ }
1428
+
1429
+ // ---------------------------------------------------------------------------
1430
+ // Inferred types
1431
+ // ---------------------------------------------------------------------------
1432
+
1433
+ // ---------------------------------------------------------------------------
1434
+ // Inferred types — Track A/B substrate
1435
+ // ---------------------------------------------------------------------------
1436
+
1437
+ export type AcqArtifactOwnerKind = z.infer<typeof AcqArtifactOwnerKindSchema>
1438
+ export type ListArtifactsQuery = z.infer<typeof ListArtifactsQuerySchema>
1439
+ export type CreateArtifactRequest = z.infer<typeof CreateArtifactRequestSchema>
1440
+ export type AcqArtifactResponse = z.infer<typeof AcqArtifactResponseSchema>
1441
+ export type AcqArtifactListResponse = z.infer<typeof AcqArtifactListResponseSchema>
1442
+ export type ListMembersQuery = z.infer<typeof ListMembersQuerySchema>
1443
+ export type ListRecordEntity = z.infer<typeof ListRecordEntitySchema>
1444
+ export type ListRecordsQuery = z.infer<typeof ListRecordsQuerySchema>
1445
+ export type MemberIdParams = z.infer<typeof MemberIdParamsSchema>
1446
+ export type AcqListMemberContactSummary = z.infer<typeof AcqListMemberContactSummarySchema>
1447
+ export type AcqListMemberResponse = z.infer<typeof AcqListMemberResponseSchema>
1448
+ export type AcqListMembersResponse = z.infer<typeof AcqListMembersResponseSchema>
1449
+ export type AcqListCompanyRecordRow = z.infer<typeof AcqListCompanyRecordRowSchema>
1450
+ export type AcqListContactRecordRow = z.infer<typeof AcqListContactRecordRowSchema>
1451
+ export type ListRecordRow = z.infer<typeof ListRecordRowSchema>
1452
+ export type ListRecordsResponse = z.infer<typeof ListRecordsResponseSchema>
1453
+ export type ListCompanyIdParams = z.infer<typeof ListCompanyIdParamsSchema>
1454
+ export type AcqListCompanyResponse = z.infer<typeof AcqListCompanyResponseSchema>
1455
+
1456
+ // ---------------------------------------------------------------------------
1457
+
1458
+ export type ListStatus = z.infer<typeof ListStatusSchema>
1459
+ export type ScrapingConfig = z.infer<typeof ScrapingConfigSchema>
1460
+ export type IcpRubric = z.infer<typeof IcpRubricSchema>
1461
+ export type PipelineStage = z.infer<typeof PipelineStageSchema>
1462
+ export type PipelineConfig = z.infer<typeof PipelineConfigSchema>
1463
+ export type BuildPlanSnapshotStep = z.infer<typeof BuildPlanSnapshotStepSchema>
1464
+ export type BuildPlanSnapshot = z.infer<typeof BuildPlanSnapshotSchema>
1465
+ export type AcqListMetadata = z.infer<typeof AcqListMetadataSchema>
1466
+ export type LeadGenActionKey = z.infer<typeof LeadGenActionKeySchema>
1467
+ export type ProcessingStageStatus = z.infer<typeof ProcessingStageStatusSchema>
1468
+ export type ListStageCountsInput = z.infer<typeof ListStageCountsSchema>['stageCounts']
1469
+ export type ListTelemetryInput = z.infer<typeof ListTelemetrySchema>
1470
+ export type ListIdParams = z.infer<typeof ListIdParamsSchema>
1471
+ export type ListReadQuery = z.infer<typeof ListReadQuerySchema>
1472
+ export type GetListQuery = z.infer<typeof GetListQuerySchema>
1473
+ export type CreateListRequest = z.infer<typeof CreateListRequestSchema>
1474
+ export type UpdateListRequest = z.infer<typeof UpdateListRequestSchema>
1475
+ export type UpdateListStatusRequest = z.infer<typeof UpdateListStatusRequestSchema>
1476
+ export type UpdateListConfigRequest = z.infer<typeof UpdateListConfigRequestSchema>
1477
+ export type AddCompaniesToListRequest = z.infer<typeof AddCompaniesToListRequestSchema>
1478
+ export type RemoveCompaniesFromListRequest = z.infer<typeof RemoveCompaniesFromListRequestSchema>
1479
+ export type AddContactsToListRequest = z.infer<typeof AddContactsToListRequestSchema>
1480
+ export type RecordListExecutionRequest = z.infer<typeof RecordListExecutionRequestSchema>
1481
+ export type AcqListResponse = z.infer<typeof AcqListResponseSchema>
1482
+ export type AcqListDetailResponse = z.infer<typeof AcqListDetailResponseSchema>
1483
+ export type AcqListListResponse = z.infer<typeof AcqListListResponseSchema>
1484
+ export type AcqListDealRef = z.infer<typeof AcqListDealRefSchema>
1485
+ export type AcqListLineage = z.infer<typeof AcqListLineageSchema>
1486
+ export type AcqListStatusResponse = z.infer<typeof AcqListStatusResponseSchema>
1487
+ export type ListTelemetryResponse = z.infer<typeof ListTelemetryResponseSchema>
1488
+ export type ListTelemetryListResponse = z.infer<typeof ListTelemetryListResponseSchema>
1489
+ export type ListExecutionSummaryInput = z.infer<typeof ListExecutionSummarySchema>
1490
+ export type ListExecutionsResponse = z.infer<typeof ListExecutionsResponseSchema>
1491
+ export type ListStageProgress = z.infer<typeof ListStageProgressSchema>
1492
+ export type ListProgress = z.infer<typeof ListProgressResponseSchema>