@elevasis/core 0.20.0 → 0.21.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 (30) hide show
  1. package/dist/index.d.ts +108 -0
  2. package/dist/index.js +177 -27
  3. package/dist/knowledge/index.d.ts +54 -0
  4. package/dist/organization-model/index.d.ts +108 -0
  5. package/dist/organization-model/index.js +177 -27
  6. package/dist/test-utils/index.d.ts +54 -0
  7. package/dist/test-utils/index.js +177 -27
  8. package/package.json +3 -3
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +6 -1
  10. package/src/business/acquisition/api-schemas.test.ts +25 -0
  11. package/src/business/acquisition/api-schemas.ts +125 -2
  12. package/src/business/acquisition/build-templates.test.ts +28 -0
  13. package/src/business/acquisition/build-templates.ts +20 -8
  14. package/src/business/acquisition/types.ts +6 -1
  15. package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.test.ts +55 -0
  16. package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +107 -41
  17. package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.test.ts +48 -0
  18. package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.ts +99 -0
  19. package/src/execution/engine/tools/integration/server/adapters/apollo/index.ts +1 -0
  20. package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.test.ts +18 -0
  21. package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.ts +194 -0
  22. package/src/execution/engine/tools/integration/server/adapters/clickup/index.ts +7 -0
  23. package/src/integrations/credentials/api-schemas.ts +21 -2
  24. package/src/integrations/credentials/schemas.ts +200 -164
  25. package/src/organization-model/__tests__/prospecting-ssot.test.ts +7 -4
  26. package/src/organization-model/domains/prospecting.ts +182 -25
  27. package/src/organization-model/domains/sales.ts +24 -3
  28. package/src/platform/constants/versions.ts +1 -1
  29. package/src/reference/_generated/contracts.md +6 -1
  30. package/src/server.ts +2 -0
@@ -19654,6 +19654,13 @@ var LEAD_GEN_STAGE_CATALOG = {
19654
19654
  order: 2,
19655
19655
  entity: "company"
19656
19656
  },
19657
+ crawled: {
19658
+ key: "crawled",
19659
+ label: "Websites crawled",
19660
+ description: "Company websites have been crawled (e.g. via Apify) and raw page content stored for downstream LLM analysis.",
19661
+ order: 2.5,
19662
+ entity: "company"
19663
+ },
19657
19664
  extracted: {
19658
19665
  key: "extracted",
19659
19666
  label: "Websites analyzed",
@@ -19673,7 +19680,9 @@ var LEAD_GEN_STAGE_CATALOG = {
19673
19680
  label: "Decision-makers found",
19674
19681
  description: "Decision-maker contacts discovered and attached to a qualified company.",
19675
19682
  order: 6,
19676
- entity: "company"
19683
+ entity: "company",
19684
+ recordEntity: "contact",
19685
+ recordStageKey: "discovered"
19677
19686
  },
19678
19687
  // Prospecting — contact discovery
19679
19688
  discovered: {
@@ -19711,7 +19720,8 @@ var LEAD_GEN_STAGE_CATALOG = {
19711
19720
  label: "Reviewed and exported",
19712
19721
  description: "Approved records have been reviewed and exported for handoff.",
19713
19722
  order: 10,
19714
- entity: "contact"
19723
+ entity: "company",
19724
+ additionalEntities: ["contact"]
19715
19725
  },
19716
19726
  interested: {
19717
19727
  key: "interested",
@@ -19781,18 +19791,47 @@ var FeatureSchema = z.object({
19781
19791
  });
19782
19792
  var ProspectingLifecycleStageSchema = DisplayMetadataSchema.extend({
19783
19793
  id: ModelIdSchema,
19784
- order: z.number().int().min(0)
19794
+ order: z.number().min(0)
19795
+ });
19796
+ var RecordColumnConfigSchema = z.object({
19797
+ key: ModelIdSchema,
19798
+ label: z.string().trim().min(1).max(120),
19799
+ path: z.string().trim().min(1).max(500),
19800
+ width: z.union([z.number().positive(), z.string().trim().min(1).max(100)]).optional(),
19801
+ renderType: z.enum(["text", "badge", "datetime", "count", "json"]).optional(),
19802
+ badgeColor: z.string().trim().min(1).max(40).optional()
19803
+ });
19804
+ var RecordColumnsConfigSchema = z.object({
19805
+ company: z.array(RecordColumnConfigSchema).optional(),
19806
+ contact: z.array(RecordColumnConfigSchema).optional()
19807
+ }).refine((columns) => Boolean(columns.company?.length || columns.contact?.length), {
19808
+ message: "recordColumns must include at least one entity column set"
19809
+ });
19810
+ var CredentialRequirementSchema = z.object({
19811
+ key: ModelIdSchema,
19812
+ provider: ModelIdSchema,
19813
+ credentialType: z.enum(["api-key", "api-key-secret", "oauth", "webhook-secret"]),
19814
+ label: z.string().trim().min(1).max(120),
19815
+ required: z.boolean(),
19816
+ selectionMode: z.enum(["single", "multiple"]).optional(),
19817
+ inputPath: z.string().trim().min(1).max(500),
19818
+ verifyOnRun: z.boolean().optional()
19785
19819
  });
19786
19820
  var ProspectingBuildTemplateStepSchema = DisplayMetadataSchema.extend({
19787
19821
  id: ModelIdSchema,
19788
19822
  primaryEntity: z.enum(["company", "contact"]),
19789
19823
  outputs: z.array(z.enum(["company", "contact", "export"])).min(1),
19790
19824
  stageKey: ModelIdSchema,
19825
+ recordEntity: z.enum(["company", "contact"]).optional(),
19826
+ recordsStageKey: ModelIdSchema.optional(),
19827
+ recordSourceStageKey: ModelIdSchema.optional(),
19791
19828
  dependsOn: z.array(ModelIdSchema).optional(),
19792
19829
  dependencyMode: z.literal("per-record-eligibility"),
19793
19830
  capabilityKey: ModelIdSchema,
19794
19831
  defaultBatchSize: z.number().int().positive(),
19795
- maxBatchSize: z.number().int().positive()
19832
+ maxBatchSize: z.number().int().positive(),
19833
+ recordColumns: RecordColumnsConfigSchema.optional(),
19834
+ credentialRequirements: z.array(CredentialRequirementSchema).optional()
19796
19835
  }).refine((step) => step.defaultBatchSize <= step.maxBatchSize, {
19797
19836
  message: "defaultBatchSize must be less than or equal to maxBatchSize",
19798
19837
  path: ["defaultBatchSize"]
@@ -19801,6 +19840,62 @@ var ProspectingBuildTemplateSchema = DisplayMetadataSchema.extend({
19801
19840
  id: ModelIdSchema,
19802
19841
  steps: z.array(ProspectingBuildTemplateStepSchema).min(1)
19803
19842
  });
19843
+ var DTC_RECORD_COLUMNS = {
19844
+ populated: {
19845
+ company: [
19846
+ { key: "name", label: "Company", path: "company.name" },
19847
+ { key: "domain", label: "Domain", path: "company.domain" },
19848
+ { key: "employee-count", label: "Employees", path: "company.numEmployees", renderType: "count" },
19849
+ { key: "apollo-industry", label: "Apollo industry", path: "company.category" },
19850
+ { key: "location", label: "Location", path: "company.locationState" }
19851
+ ]
19852
+ },
19853
+ crawled: {
19854
+ company: [
19855
+ { key: "name", label: "Company", path: "company.name" },
19856
+ { key: "domain", label: "Domain", path: "company.domain" },
19857
+ { key: "page-count", label: "Pages", path: "company.enrichmentData.websiteCrawl.pageCount", renderType: "count" },
19858
+ { key: "crawl-status", label: "Crawl status", path: "processingState.crawled.status", renderType: "badge" }
19859
+ ]
19860
+ },
19861
+ extracted: {
19862
+ company: [
19863
+ { key: "name", label: "Company", path: "company.name" },
19864
+ { key: "domain", label: "Domain", path: "company.domain" },
19865
+ { key: "description", label: "Description", path: "company.enrichmentData.websiteCrawl.companyDescription" },
19866
+ { key: "services", label: "Services", path: "company.enrichmentData.websiteCrawl.services", renderType: "json" },
19867
+ { key: "automation-gaps", label: "Automation gaps", path: "company.enrichmentData.websiteCrawl.automationGaps", renderType: "json" },
19868
+ { key: "contact-count", label: "Contacts", path: "company.enrichmentData.websiteCrawl.emailCount", renderType: "count" }
19869
+ ]
19870
+ },
19871
+ qualified: {
19872
+ company: [
19873
+ { key: "name", label: "Company", path: "company.name" },
19874
+ { key: "domain", label: "Domain", path: "company.domain" },
19875
+ { key: "score", label: "Score", path: "company.qualificationScore", renderType: "badge", badgeColor: "green" },
19876
+ { key: "signals", label: "Signals", path: "company.qualificationSignals", renderType: "json" },
19877
+ { key: "disqualified-reason", label: "Disqualified reason", path: "processingState.qualified.data.disqualifiedReason" }
19878
+ ]
19879
+ },
19880
+ decisionMakers: {
19881
+ contact: [
19882
+ { key: "name", label: "Name", path: "contact.name" },
19883
+ { key: "title", label: "Title", path: "contact.title" },
19884
+ { key: "email", label: "Email", path: "contact.email" },
19885
+ { key: "linkedin", label: "LinkedIn", path: "contact.linkedinUrl" },
19886
+ { key: "priority-score", label: "Priority", path: "contact.enrichmentData.apollo.priorityScore", renderType: "badge" }
19887
+ ]
19888
+ },
19889
+ uploaded: {
19890
+ company: [
19891
+ { key: "name", label: "Company", path: "company.name" },
19892
+ { key: "domain", label: "Domain", path: "company.domain" },
19893
+ { key: "contacts", label: "Contacts", path: "company.enrichmentData.approvedLeadListExport.contacts", renderType: "json" },
19894
+ { key: "score", label: "Score", path: "company.qualificationScore", renderType: "badge", badgeColor: "green" },
19895
+ { key: "approval", label: "Approval", path: "company.enrichmentData.approvedLeadListExport.approvalStatus", renderType: "badge" }
19896
+ ]
19897
+ }
19898
+ };
19804
19899
  z.object({
19805
19900
  id: ModelIdSchema,
19806
19901
  label: z.string(),
@@ -19904,7 +19999,45 @@ var PROSPECTING_STEPS = {
19904
19999
  dependencyMode: "per-record-eligibility",
19905
20000
  capabilityKey: "lead-gen.company.apollo-import",
19906
20001
  defaultBatchSize: 250,
19907
- maxBatchSize: 1e3
20002
+ maxBatchSize: 1e3,
20003
+ recordColumns: DTC_RECORD_COLUMNS.populated,
20004
+ credentialRequirements: [
20005
+ {
20006
+ key: "apollo",
20007
+ provider: "apollo",
20008
+ credentialType: "api-key-secret",
20009
+ label: "Apollo API key",
20010
+ required: true,
20011
+ selectionMode: "single",
20012
+ inputPath: "credential"
20013
+ }
20014
+ ]
20015
+ },
20016
+ apifyCrawl: {
20017
+ id: "apify-crawl",
20018
+ label: "Websites crawled",
20019
+ description: "Crawl company websites via Apify and store raw page markdown in enrichmentData.websiteCrawl.pages for downstream LLM analysis. Overwrites the synthetic seed Apollo Import wrote with real page content.",
20020
+ primaryEntity: "company",
20021
+ outputs: ["company"],
20022
+ stageKey: "crawled",
20023
+ dependsOn: ["import-apollo-search"],
20024
+ dependencyMode: "per-record-eligibility",
20025
+ capabilityKey: "lead-gen.company.apify-crawl",
20026
+ defaultBatchSize: 50,
20027
+ maxBatchSize: 100,
20028
+ recordColumns: DTC_RECORD_COLUMNS.crawled,
20029
+ credentialRequirements: [
20030
+ {
20031
+ key: "apify",
20032
+ provider: "apify",
20033
+ credentialType: "api-key-secret",
20034
+ label: "Apify API token",
20035
+ required: true,
20036
+ selectionMode: "single",
20037
+ inputPath: "credential",
20038
+ verifyOnRun: true
20039
+ }
20040
+ ]
19908
20041
  },
19909
20042
  analyzeWebsites: {
19910
20043
  id: "analyze-websites",
@@ -19913,11 +20046,12 @@ var PROSPECTING_STEPS = {
19913
20046
  primaryEntity: "company",
19914
20047
  outputs: ["company"],
19915
20048
  stageKey: "extracted",
19916
- dependsOn: ["import-apollo-search"],
20049
+ dependsOn: ["apify-crawl"],
19917
20050
  dependencyMode: "per-record-eligibility",
19918
20051
  capabilityKey: "lead-gen.company.website-extract",
19919
20052
  defaultBatchSize: 50,
19920
- maxBatchSize: 100
20053
+ maxBatchSize: 100,
20054
+ recordColumns: DTC_RECORD_COLUMNS.extracted
19921
20055
  },
19922
20056
  scoreDtcFit: {
19923
20057
  id: "score-dtc-fit",
@@ -19930,7 +20064,8 @@ var PROSPECTING_STEPS = {
19930
20064
  dependencyMode: "per-record-eligibility",
19931
20065
  capabilityKey: "lead-gen.company.dtc-subscription-qualify",
19932
20066
  defaultBatchSize: 100,
19933
- maxBatchSize: 250
20067
+ maxBatchSize: 250,
20068
+ recordColumns: DTC_RECORD_COLUMNS.qualified
19934
20069
  },
19935
20070
  enrichDecisionMakers: {
19936
20071
  id: "enrich-decision-makers",
@@ -19939,37 +20074,52 @@ var PROSPECTING_STEPS = {
19939
20074
  primaryEntity: "company",
19940
20075
  outputs: ["contact"],
19941
20076
  stageKey: "decision-makers-enriched",
20077
+ recordEntity: "contact",
19942
20078
  dependsOn: ["score-dtc-fit"],
19943
20079
  dependencyMode: "per-record-eligibility",
19944
20080
  capabilityKey: "lead-gen.contact.apollo-decision-maker-enrich",
19945
20081
  defaultBatchSize: 100,
19946
- maxBatchSize: 250
19947
- },
19948
- verifyEmails: {
19949
- id: "verify-emails",
19950
- label: "Emails verified",
19951
- description: "Verify deliverability before the QC and handoff step.",
19952
- primaryEntity: "contact",
19953
- outputs: ["contact"],
19954
- stageKey: "verified",
19955
- dependsOn: ["enrich-decision-makers"],
19956
- dependencyMode: "per-record-eligibility",
19957
- capabilityKey: "lead-gen.contact.verify-email",
19958
- defaultBatchSize: 250,
19959
- maxBatchSize: 500
20082
+ maxBatchSize: 250,
20083
+ recordColumns: DTC_RECORD_COLUMNS.decisionMakers,
20084
+ credentialRequirements: [
20085
+ {
20086
+ key: "apollo",
20087
+ provider: "apollo",
20088
+ credentialType: "api-key-secret",
20089
+ label: "Apollo API key",
20090
+ required: true,
20091
+ selectionMode: "single",
20092
+ inputPath: "credential"
20093
+ }
20094
+ ]
19960
20095
  },
19961
20096
  reviewAndExport: {
19962
20097
  id: "review-and-export",
19963
20098
  label: "Reviewed and exported",
19964
- description: "Operator QC approves or rejects leads, then approved records are exported as a lead list.",
20099
+ description: "Operator QC approves or rejects qualified companies, then approved records are exported as a lead list with unverified emails.",
19965
20100
  primaryEntity: "company",
19966
20101
  outputs: ["export"],
19967
20102
  stageKey: "uploaded",
19968
- dependsOn: ["verify-emails"],
20103
+ recordsStageKey: "uploaded",
20104
+ recordSourceStageKey: "qualified",
20105
+ dependsOn: ["enrich-decision-makers"],
19969
20106
  dependencyMode: "per-record-eligibility",
19970
20107
  capabilityKey: "lead-gen.export.list",
19971
20108
  defaultBatchSize: 100,
19972
- maxBatchSize: 250
20109
+ maxBatchSize: 250,
20110
+ recordColumns: DTC_RECORD_COLUMNS.uploaded,
20111
+ credentialRequirements: [
20112
+ {
20113
+ key: "clickup",
20114
+ provider: "clickup",
20115
+ credentialType: "api-key-secret",
20116
+ label: "ClickUp API token",
20117
+ required: true,
20118
+ selectionMode: "single",
20119
+ inputPath: "clickupCredential",
20120
+ verifyOnRun: true
20121
+ }
20122
+ ]
19973
20123
  }
19974
20124
  }
19975
20125
  };
@@ -19991,7 +20141,7 @@ function toProspectingLifecycleStage(stage) {
19991
20141
  };
19992
20142
  }
19993
20143
  function leadGenStagesForEntity(entity) {
19994
- return Object.values(LEAD_GEN_STAGE_CATALOG).filter((stage) => stage.entity === entity).sort((a3, b2) => a3.order - b2.order).map(toProspectingLifecycleStage);
20144
+ return Object.values(LEAD_GEN_STAGE_CATALOG).filter((stage) => stage.entity === entity || stage.additionalEntities?.includes(entity)).sort((a3, b2) => a3.order - b2.order).map(toProspectingLifecycleStage);
19995
20145
  }
19996
20146
  var DEFAULT_ORGANIZATION_MODEL_PROSPECTING = {
19997
20147
  listEntityId: "leadgen.list",
@@ -20021,10 +20171,10 @@ var DEFAULT_ORGANIZATION_MODEL_PROSPECTING = {
20021
20171
  description: "Prospecting pipeline for DTC subscription or subscription-ready brands where Apollo is the source and contact-enrichment layer, Elevasis handles company research and fit scoring, and approved leads export as an approved lead list.",
20022
20172
  steps: [
20023
20173
  PROSPECTING_STEPS.dtcApolloClickup.importApolloSearch,
20174
+ PROSPECTING_STEPS.dtcApolloClickup.apifyCrawl,
20024
20175
  PROSPECTING_STEPS.dtcApolloClickup.analyzeWebsites,
20025
20176
  PROSPECTING_STEPS.dtcApolloClickup.scoreDtcFit,
20026
20177
  PROSPECTING_STEPS.dtcApolloClickup.enrichDecisionMakers,
20027
- PROSPECTING_STEPS.dtcApolloClickup.verifyEmails,
20028
20178
  PROSPECTING_STEPS.dtcApolloClickup.reviewAndExport
20029
20179
  ]
20030
20180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevasis/core",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "license": "MIT",
5
5
  "description": "Minimal shared constants across Elevasis monorepo",
6
6
  "sideEffects": false,
@@ -41,8 +41,8 @@
41
41
  "rollup-plugin-dts": "^6.3.0",
42
42
  "tsup": "^8.0.0",
43
43
  "typescript": "5.9.2",
44
- "@repo/typescript-config": "0.0.0",
45
- "@repo/eslint-config": "0.0.0"
44
+ "@repo/eslint-config": "0.0.0",
45
+ "@repo/typescript-config": "0.0.0"
46
46
  },
47
47
  "dependencies": {
48
48
  "@anthropic-ai/sdk": "^0.62.0",
@@ -2549,9 +2549,14 @@ export const AcqSubstrateSchemas = {
2549
2549
 
2550
2550
  // List members
2551
2551
  ListMembersQuery: ListMembersQuerySchema,
2552
+ ListRecordsQuery: ListRecordsQuerySchema,
2552
2553
  MemberIdParams: MemberIdParamsSchema,
2553
2554
  AcqListMemberResponse: AcqListMemberResponseSchema,
2554
2555
  AcqListMembersResponse: AcqListMembersResponseSchema,
2556
+ AcqListCompanyRecordRow: AcqListCompanyRecordRowSchema,
2557
+ AcqListContactRecordRow: AcqListContactRecordRowSchema,
2558
+ ListRecordRow: ListRecordRowSchema,
2559
+ ListRecordsResponse: ListRecordsResponseSchema,
2555
2560
 
2556
2561
  // List companies
2557
2562
  ListCompanyIdParams: ListCompanyIdParamsSchema,
@@ -2709,7 +2714,7 @@ export const ACQ_LIST_COMPANIES_LEAD_GEN_PIPELINE: StatefulPipelineDefinition =
2709
2714
  {
2710
2715
  stageKey: 'outreach',
2711
2716
  label: 'Outreach',
2712
- states: [PENDING_STATE]
2717
+ states: [PENDING_STATE, { stateKey: 'uploaded', label: 'Uploaded' }]
2713
2718
  },
2714
2719
  {
2715
2720
  stageKey: 'prospecting',
@@ -38,6 +38,7 @@ import {
38
38
  ListDealsQuerySchema,
39
39
  ListDealTasksDueQuerySchema,
40
40
  ListMembersQuerySchema,
41
+ ListRecordsQuerySchema,
41
42
  ListStatusSchema,
42
43
  PipelineStageSchema,
43
44
  ScrapingConfigSchema,
@@ -1601,6 +1602,30 @@ describe('ListMembersQuerySchema', () => {
1601
1602
  })
1602
1603
  })
1603
1604
 
1605
+ // ---------------------------------------------------------------------------
1606
+ // ListRecordsQuerySchema
1607
+ // ---------------------------------------------------------------------------
1608
+
1609
+ describe('ListRecordsQuerySchema', () => {
1610
+ it('accepts contact records for DTC decision-maker enrichment', () => {
1611
+ const result = ListRecordsQuerySchema.safeParse({
1612
+ entity: 'contact',
1613
+ stage: 'decision-makers-enriched'
1614
+ })
1615
+
1616
+ expect(result.success).toBe(true)
1617
+ })
1618
+
1619
+ it('keeps company records valid for qualified and uploaded stages', () => {
1620
+ expect(ListRecordsQuerySchema.safeParse({ entity: 'company', stage: 'qualified' }).success).toBe(true)
1621
+ expect(ListRecordsQuerySchema.safeParse({ entity: 'company', stage: 'uploaded' }).success).toBe(true)
1622
+ })
1623
+
1624
+ it('still rejects unrelated stage/entity combinations', () => {
1625
+ expect(ListRecordsQuerySchema.safeParse({ entity: 'contact', stage: 'qualified' }).success).toBe(false)
1626
+ })
1627
+ })
1628
+
1604
1629
  // ---------------------------------------------------------------------------
1605
1630
  // AcqListResponseSchema (forward-compat)
1606
1631
  // ---------------------------------------------------------------------------
@@ -1,7 +1,11 @@
1
1
  import { z } from 'zod'
2
2
  import { UuidSchema, NonEmptyStringSchema } from '../../platform/utils/validation'
3
3
  import { CRM_PIPELINE_DEFINITION, LEAD_GEN_STAGE_CATALOG } from '../../organization-model/domains/sales'
4
- import { CAPABILITY_REGISTRY } from '../../organization-model/domains/prospecting'
4
+ import {
5
+ CAPABILITY_REGISTRY,
6
+ CredentialRequirementSchema,
7
+ RecordColumnConfigSchema
8
+ } from '../../organization-model/domains/prospecting'
5
9
  import { isProspectingBuildTemplateId } from './build-templates'
6
10
  export { CrmPriorityBucketKeySchema, CrmPriorityBucketOverrideSchema, CrmPriorityOverrideSchema } from './crm-priority'
7
11
  export type { CrmPriorityBucketOverride, CrmPriorityOverride, ResolvedCrmPriorityRuleConfig } from './crm-priority'
@@ -496,11 +500,21 @@ export const BuildPlanSnapshotStepSchema = z
496
500
  primaryEntity: z.enum(['company', 'contact']),
497
501
  outputs: z.array(z.enum(['company', 'contact', 'export'])).min(1),
498
502
  stageKey: LeadGenStageKeySchema,
503
+ recordEntity: z.enum(['company', 'contact']).optional(),
504
+ recordsStageKey: LeadGenStageKeySchema.optional(),
505
+ recordSourceStageKey: LeadGenStageKeySchema.optional(),
499
506
  dependsOn: z.array(z.string().trim().min(1).max(100)).optional(),
500
507
  dependencyMode: z.literal('per-record-eligibility'),
501
508
  capabilityKey: LeadGenCapabilityKeySchema,
502
509
  defaultBatchSize: z.number().int().positive(),
503
- maxBatchSize: z.number().int().positive()
510
+ maxBatchSize: z.number().int().positive(),
511
+ recordColumns: z
512
+ .object({
513
+ company: z.array(RecordColumnConfigSchema).optional(),
514
+ contact: z.array(RecordColumnConfigSchema).optional()
515
+ })
516
+ .optional(),
517
+ credentialRequirements: z.array(CredentialRequirementSchema).optional()
504
518
  })
505
519
  .refine((step) => step.defaultBatchSize <= step.maxBatchSize, {
506
520
  message: 'defaultBatchSize must be less than or equal to maxBatchSize',
@@ -1063,6 +1077,33 @@ export const ListMembersQuerySchema = z
1063
1077
  })
1064
1078
  .strict()
1065
1079
 
1080
+ export const ListRecordEntitySchema = z.enum(['company', 'contact'])
1081
+
1082
+ export const ListRecordsQuerySchema = z
1083
+ .object({
1084
+ entity: ListRecordEntitySchema,
1085
+ stage: LeadGenStageKeySchema.optional(),
1086
+ limit: z.coerce.number().int().min(1).max(500).default(50),
1087
+ offset: z.coerce.number().int().min(0).default(0)
1088
+ })
1089
+ .strict()
1090
+ .superRefine((query, ctx) => {
1091
+ if (!query.stage) return
1092
+
1093
+ const stage = LEAD_GEN_STAGE_CATALOG[query.stage]
1094
+ const validEntity =
1095
+ stage?.entity === query.entity ||
1096
+ stage?.additionalEntities?.includes(query.entity) ||
1097
+ stage?.recordEntity === query.entity
1098
+ if (!validEntity) {
1099
+ ctx.addIssue({
1100
+ code: z.ZodIssueCode.custom,
1101
+ message: `stage "${query.stage}" is not valid for ${query.entity} records`,
1102
+ path: ['stage']
1103
+ })
1104
+ }
1105
+ })
1106
+
1066
1107
  export const MemberIdParamsSchema = z.object({
1067
1108
  memberId: UuidSchema
1068
1109
  })
@@ -1095,6 +1136,77 @@ export const AcqListMembersResponseSchema = z.object({
1095
1136
  members: z.array(AcqListMemberResponseSchema)
1096
1137
  })
1097
1138
 
1139
+ export const AcqListRecordCompanySummarySchema = z.object({
1140
+ id: z.string(),
1141
+ name: z.string(),
1142
+ domain: z.string().nullable(),
1143
+ website: z.string().nullable(),
1144
+ linkedinUrl: z.string().nullable(),
1145
+ numEmployees: z.number().nullable(),
1146
+ foundedYear: z.number().nullable(),
1147
+ locationCity: z.string().nullable(),
1148
+ locationState: z.string().nullable(),
1149
+ category: z.string().nullable(),
1150
+ segment: z.string().nullable(),
1151
+ status: AcqCompanyStatusSchema,
1152
+ qualificationScore: z.number().nullable(),
1153
+ qualificationSignals: z.record(z.string(), z.unknown()).nullable(),
1154
+ qualificationRubricKey: z.string().nullable()
1155
+ })
1156
+
1157
+ export const AcqListRecordContactSummarySchema = z.object({
1158
+ id: z.string(),
1159
+ email: z.string(),
1160
+ firstName: z.string().nullable(),
1161
+ lastName: z.string().nullable(),
1162
+ title: z.string().nullable(),
1163
+ headline: z.string().nullable(),
1164
+ linkedinUrl: z.string().nullable(),
1165
+ companyId: z.string().nullable(),
1166
+ status: AcqContactStatusSchema,
1167
+ qualificationScore: z.number().nullable(),
1168
+ qualificationSignals: z.record(z.string(), z.unknown()).nullable(),
1169
+ qualificationRubricKey: z.string().nullable()
1170
+ })
1171
+
1172
+ const ListRecordBaseSchema = z.object({
1173
+ id: z.string(),
1174
+ listId: z.string(),
1175
+ pipelineKey: z.string(),
1176
+ stageKey: z.string(),
1177
+ stateKey: z.string(),
1178
+ activityLog: z.unknown(),
1179
+ addedAt: z.string(),
1180
+ addedBy: z.string().nullable(),
1181
+ sourceExecutionId: z.string().nullable(),
1182
+ processingState: z.record(z.string(), z.unknown()).nullable(),
1183
+ enrichmentData: z.record(z.string(), z.unknown()).nullable()
1184
+ })
1185
+
1186
+ export const AcqListCompanyRecordRowSchema = ListRecordBaseSchema.extend({
1187
+ entity: z.literal('company'),
1188
+ companyId: z.string(),
1189
+ company: AcqListRecordCompanySummarySchema.nullable()
1190
+ })
1191
+
1192
+ export const AcqListContactRecordRowSchema = ListRecordBaseSchema.extend({
1193
+ entity: z.literal('contact'),
1194
+ contactId: z.string(),
1195
+ contact: AcqListRecordContactSummarySchema.nullable()
1196
+ })
1197
+
1198
+ export const ListRecordRowSchema = z.discriminatedUnion('entity', [
1199
+ AcqListCompanyRecordRowSchema,
1200
+ AcqListContactRecordRowSchema
1201
+ ])
1202
+
1203
+ export const ListRecordsResponseSchema = z.object({
1204
+ data: z.array(ListRecordRowSchema),
1205
+ total: z.number().int().min(0),
1206
+ limit: z.number().int().min(1),
1207
+ offset: z.number().int().min(0)
1208
+ })
1209
+
1098
1210
  // ---------------------------------------------------------------------------
1099
1211
  // Track B: List Companies API Schemas
1100
1212
  // ---------------------------------------------------------------------------
@@ -1216,9 +1328,14 @@ export const AcqSubstrateSchemas = {
1216
1328
 
1217
1329
  // List members
1218
1330
  ListMembersQuery: ListMembersQuerySchema,
1331
+ ListRecordsQuery: ListRecordsQuerySchema,
1219
1332
  MemberIdParams: MemberIdParamsSchema,
1220
1333
  AcqListMemberResponse: AcqListMemberResponseSchema,
1221
1334
  AcqListMembersResponse: AcqListMembersResponseSchema,
1335
+ AcqListCompanyRecordRow: AcqListCompanyRecordRowSchema,
1336
+ AcqListContactRecordRow: AcqListContactRecordRowSchema,
1337
+ ListRecordRow: ListRecordRowSchema,
1338
+ ListRecordsResponse: ListRecordsResponseSchema,
1222
1339
 
1223
1340
  // List companies
1224
1341
  ListCompanyIdParams: ListCompanyIdParamsSchema,
@@ -1242,10 +1359,16 @@ export type CreateArtifactRequest = z.infer<typeof CreateArtifactRequestSchema>
1242
1359
  export type AcqArtifactResponse = z.infer<typeof AcqArtifactResponseSchema>
1243
1360
  export type AcqArtifactListResponse = z.infer<typeof AcqArtifactListResponseSchema>
1244
1361
  export type ListMembersQuery = z.infer<typeof ListMembersQuerySchema>
1362
+ export type ListRecordEntity = z.infer<typeof ListRecordEntitySchema>
1363
+ export type ListRecordsQuery = z.infer<typeof ListRecordsQuerySchema>
1245
1364
  export type MemberIdParams = z.infer<typeof MemberIdParamsSchema>
1246
1365
  export type AcqListMemberContactSummary = z.infer<typeof AcqListMemberContactSummarySchema>
1247
1366
  export type AcqListMemberResponse = z.infer<typeof AcqListMemberResponseSchema>
1248
1367
  export type AcqListMembersResponse = z.infer<typeof AcqListMembersResponseSchema>
1368
+ export type AcqListCompanyRecordRow = z.infer<typeof AcqListCompanyRecordRowSchema>
1369
+ export type AcqListContactRecordRow = z.infer<typeof AcqListContactRecordRowSchema>
1370
+ export type ListRecordRow = z.infer<typeof ListRecordRowSchema>
1371
+ export type ListRecordsResponse = z.infer<typeof ListRecordsResponseSchema>
1249
1372
  export type ListCompanyIdParams = z.infer<typeof ListCompanyIdParamsSchema>
1250
1373
  export type AcqListCompanyResponse = z.infer<typeof AcqListCompanyResponseSchema>
1251
1374
 
@@ -180,12 +180,40 @@ describe('createBuildPlanSnapshotFromTemplateId — "dtc-subscription-apollo-cli
180
180
  expect(first?.outputs).toContain('contact')
181
181
  })
182
182
 
183
+ it('preserves Apollo credential requirements for source import and decision-maker enrichment', () => {
184
+ const importApolloSearch = snapshot?.steps.find((step) => step.id === 'import-apollo-search')
185
+ const enrichDecisionMakers = snapshot?.steps.find((step) => step.id === 'enrich-decision-makers')
186
+
187
+ const expectedRequirement = {
188
+ key: 'apollo',
189
+ provider: 'apollo',
190
+ credentialType: 'api-key-secret',
191
+ label: 'Apollo API key',
192
+ required: true,
193
+ selectionMode: 'single',
194
+ inputPath: 'credential'
195
+ }
196
+
197
+ expect(importApolloSearch?.credentialRequirements).toEqual([expectedRequirement])
198
+ expect(enrichDecisionMakers?.credentialRequirements).toEqual([expectedRequirement])
199
+ })
200
+
183
201
  it('the final step (review-and-export) outputs export', () => {
184
202
  const last = snapshot?.steps[snapshot.steps.length - 1]
185
203
  expect(last?.id).toBe('review-and-export')
186
204
  expect(last?.outputs).toContain('export')
187
205
  })
188
206
 
207
+ it('preserves contact records metadata for the company-owned decision-maker step', () => {
208
+ const decisionMakers = snapshot?.steps.find((step) => step.id === 'enrich-decision-makers')
209
+ expect(decisionMakers).toMatchObject({
210
+ primaryEntity: 'company',
211
+ recordEntity: 'contact',
212
+ stageKey: 'decision-makers-enriched'
213
+ })
214
+ expect(decisionMakers?.recordColumns?.contact?.length).toBeGreaterThan(0)
215
+ })
216
+
189
217
  it('steps with descriptions in the catalog include description in the snapshot', () => {
190
218
  const withDesc = snapshot?.steps.filter((step) => step.description !== undefined) ?? []
191
219
  expect(withDesc.length).toBeGreaterThan(0)
@@ -29,16 +29,28 @@ export function createBuildPlanSnapshotFromTemplateId(templateId: string): Build
29
29
  primaryEntity: step.primaryEntity,
30
30
  outputs: [...step.outputs],
31
31
  stageKey: step.stageKey,
32
+ recordsStageKey: step.recordsStageKey ?? step.stageKey,
33
+ recordSourceStageKey: step.recordSourceStageKey ?? step.recordsStageKey ?? step.stageKey,
32
34
  dependencyMode: step.dependencyMode,
33
35
  capabilityKey: step.capabilityKey,
34
36
  defaultBatchSize: step.defaultBatchSize,
35
37
  maxBatchSize: step.maxBatchSize
36
- }
37
-
38
- if (step.description) snapshotStep.description = step.description
39
- if (step.dependsOn?.length) snapshotStep.dependsOn = [...step.dependsOn]
40
-
41
- return snapshotStep
42
- })
43
- }
38
+ }
39
+
40
+ if (step.description) snapshotStep.description = step.description
41
+ if (step.recordEntity) snapshotStep.recordEntity = step.recordEntity
42
+ if (step.dependsOn?.length) snapshotStep.dependsOn = [...step.dependsOn]
43
+ if (step.credentialRequirements?.length) {
44
+ snapshotStep.credentialRequirements = step.credentialRequirements.map((requirement) => ({ ...requirement }))
45
+ }
46
+ if (step.recordColumns) {
47
+ snapshotStep.recordColumns = {
48
+ ...(step.recordColumns.company ? { company: step.recordColumns.company.map((column) => ({ ...column })) } : {}),
49
+ ...(step.recordColumns.contact ? { contact: step.recordColumns.contact.map((column) => ({ ...column })) } : {})
50
+ }
51
+ }
52
+
53
+ return snapshotStep
54
+ })
55
+ }
44
56
  }
@@ -1,5 +1,5 @@
1
1
  import type { Database } from '../../supabase/database.types'
2
- import type { Capability } from '../../organization-model/domains/prospecting'
2
+ import type { Capability, CredentialRequirement, RecordColumnConfig } from '../../organization-model/domains/prospecting'
3
3
  import type { LEAD_GEN_STAGE_CATALOG } from '../../organization-model/domains/sales'
4
4
  import type { PipelineStage, ProcessingStageStatus } from './api-schemas'
5
5
 
@@ -167,11 +167,16 @@ export interface BuildPlanSnapshotStep {
167
167
  primaryEntity: BuildPlanSnapshotPrimaryEntity
168
168
  outputs: BuildPlanSnapshotOutput[]
169
169
  stageKey: string
170
+ recordEntity?: BuildPlanSnapshotPrimaryEntity
171
+ recordsStageKey?: string
172
+ recordSourceStageKey?: string
170
173
  dependsOn?: string[]
171
174
  dependencyMode: BuildPlanSnapshotDependencyMode
172
175
  capabilityKey: string
173
176
  defaultBatchSize: number
174
177
  maxBatchSize: number
178
+ recordColumns?: Partial<Record<BuildPlanSnapshotPrimaryEntity, RecordColumnConfig[]>>
179
+ credentialRequirements?: CredentialRequirement[]
175
180
  }
176
181
 
177
182
  export interface BuildPlanSnapshot {