@elevasis/core 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/index.d.ts +5289 -0
- package/dist/auth/index.js +595 -0
- package/dist/index.d.ts +11 -11
- package/dist/knowledge/index.d.ts +1 -1
- package/dist/organization-model/index.d.ts +11 -11
- package/dist/test-utils/index.d.ts +24 -1
- package/package.json +7 -3
- package/src/__tests__/publish.test.ts +8 -7
- package/src/auth/__tests__/access-key-coverage.test.ts +42 -0
- package/src/auth/__tests__/access-key-scan.ts +117 -0
- package/src/auth/__tests__/access-keys.test.ts +81 -0
- package/src/auth/__tests__/access-model.test.ts +257 -0
- package/src/auth/__tests__/access-test-fixtures.ts +50 -0
- package/src/auth/__tests__/key-catalog-drift.test.ts +33 -0
- package/src/auth/__tests__/platform-admin-bypass-parity.test.ts +67 -0
- package/src/auth/access-keys.ts +173 -0
- package/src/auth/access-model.ts +185 -0
- package/src/auth/index.ts +6 -2
- package/src/auth/multi-tenancy/memberships/membership.ts +2 -4
- package/src/auth/multi-tenancy/permissions.ts +1 -1
- package/src/auth/multi-tenancy/types.ts +3 -12
- package/src/business/acquisition/api-schemas.test.ts +59 -8
- package/src/business/acquisition/api-schemas.ts +10 -5
- package/src/business/acquisition/build-templates.test.ts +187 -240
- package/src/business/acquisition/build-templates.ts +87 -98
- package/src/business/acquisition/types.ts +390 -389
- package/src/execution/engine/index.ts +6 -4
- package/src/execution/engine/tools/lead-service-types.ts +63 -34
- package/src/execution/engine/tools/platform/acquisition/types.ts +7 -8
- package/src/execution/engine/tools/registry.ts +6 -4
- package/src/execution/engine/tools/tool-maps.ts +23 -1
- package/src/organization-model/domains/prospecting.ts +2 -327
- package/src/organization-model/migration-helpers.ts +16 -12
- package/src/reference/_generated/contracts.md +352 -328
- package/src/reference/glossary.md +8 -6
- package/src/supabase/database.types.ts +13 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ACCESS_ACTION,
|
|
3
|
+
PLATFORM_ADMIN_ACCESS_KEY,
|
|
4
|
+
deriveAccessKeyCatalog,
|
|
5
|
+
findAccessCatalogEntry,
|
|
6
|
+
normalizeAccessKey,
|
|
7
|
+
type AccessKeyCatalog,
|
|
8
|
+
type AccessKeyInput,
|
|
9
|
+
type NormalizedAccessKey
|
|
10
|
+
} from './access-keys'
|
|
11
|
+
import { getSystem } from '../organization-model/helpers'
|
|
12
|
+
import type { OrganizationModel, OrganizationModelSystemLifecycle } from '../organization-model/types'
|
|
13
|
+
|
|
14
|
+
export type AccessRestrictedBy =
|
|
15
|
+
| 'catalog'
|
|
16
|
+
| 'membership'
|
|
17
|
+
| 'system-lifecycle'
|
|
18
|
+
| 'role-permission'
|
|
19
|
+
| 'diagnostic-allowlist'
|
|
20
|
+
| null
|
|
21
|
+
|
|
22
|
+
export type AccessReason =
|
|
23
|
+
| 'allowed'
|
|
24
|
+
| 'platform-admin-bypass'
|
|
25
|
+
| 'invalid-access-key'
|
|
26
|
+
| 'unknown-access-key'
|
|
27
|
+
| 'organization-mismatch'
|
|
28
|
+
| 'missing-membership'
|
|
29
|
+
| 'system-not-active'
|
|
30
|
+
| 'role-permission-denied'
|
|
31
|
+
| 'diagnostic-key-not-allowed'
|
|
32
|
+
|
|
33
|
+
export interface AccessAnswer {
|
|
34
|
+
allowed: boolean
|
|
35
|
+
restrictedBy: AccessRestrictedBy
|
|
36
|
+
reason: AccessReason
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AccessContextMembership {
|
|
40
|
+
id?: string
|
|
41
|
+
organizationId: string
|
|
42
|
+
role?: string | null
|
|
43
|
+
effectivePermissions?: readonly string[] | null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AccessContextProfile {
|
|
47
|
+
id?: string
|
|
48
|
+
isPlatformAdmin?: boolean | null
|
|
49
|
+
is_platform_admin?: boolean | null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface AccessContext {
|
|
53
|
+
organizationId: string
|
|
54
|
+
organizationModel: OrganizationModel
|
|
55
|
+
membership?: AccessContextMembership | null
|
|
56
|
+
profile?: AccessContextProfile | null
|
|
57
|
+
permissions?: readonly string[] | null
|
|
58
|
+
diagnosticAllowlist?: ReadonlySet<string> | readonly string[]
|
|
59
|
+
accessCatalog?: AccessKeyCatalog
|
|
60
|
+
betaAccessEnabled?: boolean
|
|
61
|
+
isDevelopment?: boolean
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface AccessModel {
|
|
65
|
+
catalog: AccessKeyCatalog
|
|
66
|
+
checkAccess: (key: AccessKeyInput, ctx: Omit<AccessContext, 'accessCatalog'>) => AccessAnswer
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const ALLOWED: AccessAnswer = { allowed: true, restrictedBy: null, reason: 'allowed' }
|
|
70
|
+
const PLATFORM_ADMIN_BYPASS: AccessAnswer = {
|
|
71
|
+
allowed: true,
|
|
72
|
+
restrictedBy: null,
|
|
73
|
+
reason: 'platform-admin-bypass'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function deny(restrictedBy: Exclude<AccessRestrictedBy, null>, reason: Exclude<AccessReason, 'allowed'>): AccessAnswer {
|
|
77
|
+
return { allowed: false, restrictedBy, reason }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isPlatformAdmin(profile: AccessContextProfile | null | undefined): boolean {
|
|
81
|
+
return profile?.isPlatformAdmin === true || profile?.is_platform_admin === true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function diagnosticAllowlistHas(
|
|
85
|
+
allowlist: AccessContext['diagnosticAllowlist'],
|
|
86
|
+
systemPath: string
|
|
87
|
+
): boolean {
|
|
88
|
+
if (allowlist === undefined) return false
|
|
89
|
+
return 'has' in allowlist ? allowlist.has(systemPath) : allowlist.includes(systemPath)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function lifecycleAllowsAccess(
|
|
93
|
+
lifecycle: OrganizationModelSystemLifecycle | undefined,
|
|
94
|
+
ctx: Pick<AccessContext, 'betaAccessEnabled' | 'isDevelopment'>
|
|
95
|
+
): boolean {
|
|
96
|
+
if (lifecycle === 'active') return true
|
|
97
|
+
if (lifecycle === 'beta') return ctx.betaAccessEnabled === true || ctx.isDevelopment === true
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getPermissionSource(ctx: AccessContext): readonly string[] | null | undefined {
|
|
102
|
+
return ctx.permissions ?? ctx.membership?.effectivePermissions
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function hasRequiredPermission(key: NormalizedAccessKey, rolePermission: string | undefined, ctx: AccessContext): boolean {
|
|
106
|
+
const permissionSource = getPermissionSource(ctx)
|
|
107
|
+
if (permissionSource === undefined || permissionSource === null) return true
|
|
108
|
+
|
|
109
|
+
const requiredPermission = rolePermission ?? `${key.systemPath}.${key.action}`
|
|
110
|
+
return permissionSource.includes(requiredPermission)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function hasExplicitRequiredPermission(rolePermission: string | undefined, ctx: AccessContext): boolean {
|
|
114
|
+
const permissionSource = getPermissionSource(ctx)
|
|
115
|
+
return rolePermission !== undefined && permissionSource !== undefined && permissionSource !== null
|
|
116
|
+
? permissionSource.includes(rolePermission)
|
|
117
|
+
: false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function checkAccess(input: AccessKeyInput, ctx: AccessContext): AccessAnswer {
|
|
121
|
+
if (isPlatformAdmin(ctx.profile)) return PLATFORM_ADMIN_BYPASS
|
|
122
|
+
|
|
123
|
+
const parsed = (() => {
|
|
124
|
+
try {
|
|
125
|
+
return normalizeAccessKey(input)
|
|
126
|
+
} catch {
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
})()
|
|
130
|
+
|
|
131
|
+
if (parsed === null) return deny('catalog', 'invalid-access-key')
|
|
132
|
+
|
|
133
|
+
const catalog = ctx.accessCatalog ?? deriveAccessKeyCatalog(ctx.organizationModel)
|
|
134
|
+
const catalogEntry = findAccessCatalogEntry(catalog, parsed)
|
|
135
|
+
|
|
136
|
+
if (catalogEntry === undefined) return deny('catalog', 'unknown-access-key')
|
|
137
|
+
|
|
138
|
+
const membership = ctx.membership
|
|
139
|
+
if (membership === undefined || membership === null) return deny('membership', 'missing-membership')
|
|
140
|
+
if (membership.organizationId !== ctx.organizationId) return deny('membership', 'organization-mismatch')
|
|
141
|
+
|
|
142
|
+
if (parsed.systemPath === PLATFORM_ADMIN_ACCESS_KEY) {
|
|
143
|
+
return deny('role-permission', 'role-permission-denied')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (catalogEntry.source === 'diagnostic') {
|
|
147
|
+
if (!diagnosticAllowlistHas(ctx.diagnosticAllowlist, parsed.systemPath)) {
|
|
148
|
+
return deny('diagnostic-allowlist', 'diagnostic-key-not-allowed')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (parsed.action !== DEFAULT_ACCESS_ACTION && !hasRequiredPermission(parsed, catalogEntry.rolePermission, ctx)) {
|
|
152
|
+
return deny('role-permission', 'role-permission-denied')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return ALLOWED
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (catalogEntry.source === 'permission') {
|
|
159
|
+
if (!hasExplicitRequiredPermission(catalogEntry.rolePermission, ctx)) {
|
|
160
|
+
return deny('role-permission', 'role-permission-denied')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return ALLOWED
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const system = getSystem(ctx.organizationModel, parsed.systemPath)
|
|
167
|
+
if (system === undefined || !lifecycleAllowsAccess(system.lifecycle, ctx)) {
|
|
168
|
+
return deny('system-lifecycle', 'system-not-active')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (parsed.action !== DEFAULT_ACCESS_ACTION && !hasRequiredPermission(parsed, catalogEntry.rolePermission, ctx)) {
|
|
172
|
+
return deny('role-permission', 'role-permission-denied')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return ALLOWED
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function createAccessModel(organizationModel: OrganizationModel): AccessModel {
|
|
179
|
+
const catalog = deriveAccessKeyCatalog(organizationModel)
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
catalog,
|
|
183
|
+
checkAccess: (key, ctx) => checkAccess(key, { ...ctx, organizationModel, accessCatalog: catalog })
|
|
184
|
+
}
|
|
185
|
+
}
|
package/src/auth/index.ts
CHANGED
|
@@ -4,5 +4,9 @@
|
|
|
4
4
|
* Identity and multi-tenancy types
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
// Multi-tenancy types (browser-safe only)
|
|
8
|
-
export * from './multi-tenancy/index'
|
|
7
|
+
// Multi-tenancy types (browser-safe only)
|
|
8
|
+
export * from './multi-tenancy/index'
|
|
9
|
+
|
|
10
|
+
// Unified access model primitives (browser-safe only)
|
|
11
|
+
export * from './access-keys'
|
|
12
|
+
export * from './access-model'
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { type MembershipStatus } from './api-schemas.js'
|
|
1
|
+
import { type MembershipStatus } from './api-schemas.js'
|
|
3
2
|
|
|
4
3
|
export type { MembershipStatus }
|
|
5
4
|
|
|
@@ -75,8 +74,7 @@ export interface MembershipWithDetails extends OrganizationMembership {
|
|
|
75
74
|
metadata?: Record<string, unknown>
|
|
76
75
|
config?: Record<string, unknown>
|
|
77
76
|
}
|
|
78
|
-
|
|
79
|
-
}
|
|
77
|
+
}
|
|
80
78
|
|
|
81
79
|
/**
|
|
82
80
|
* UI-specific membership table row data
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Source of truth for the permission keys used by:
|
|
5
5
|
* - RLS policies in Supabase (via has_org_permission(org_id, key))
|
|
6
6
|
* - API middleware (via requireOrganizationPermission(key))
|
|
7
|
-
* - UI
|
|
7
|
+
* - UI access checks (via useAccess() permission-backed AccessKeys)
|
|
8
8
|
*
|
|
9
9
|
* The DB table `org_rol_permissions` mirrors this constant. There is no
|
|
10
10
|
* runtime reconciler; parity is enforced two ways:
|
|
@@ -3,23 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Config is stored in dedicated `config` columns (NOT nested in metadata):
|
|
5
5
|
* - organizations.config: Org-level config (no feature toggles -- all features available by default)
|
|
6
|
-
* - org_memberships.config:
|
|
6
|
+
* - org_memberships.config: Reserved for non-access membership configuration
|
|
7
7
|
* - users.config: User-global config
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ThemePresetName } from './theme-presets'
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
14
|
-
* Controls which features a specific member can access within their org.
|
|
15
|
-
* Keys are feature IDs from the organization model (e.g. crm, lead-gen, projects, seo).
|
|
16
|
-
*/
|
|
17
|
-
export interface MembershipFeatureConfig {
|
|
18
|
-
features?: Record<string, boolean>
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* User-global config (stored in users.config)
|
|
12
|
+
/**
|
|
13
|
+
* User-global config (stored in users.config)
|
|
23
14
|
* Theme and onboarding are user-specific, NOT org-specific
|
|
24
15
|
*/
|
|
25
16
|
export interface UserConfig {
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
ListReadQuerySchema,
|
|
45
45
|
ListRecordsQuerySchema,
|
|
46
46
|
ListStatusSchema,
|
|
47
|
+
PipelineConfigSchema,
|
|
47
48
|
PipelineStageSchema,
|
|
48
49
|
ScrapingConfigSchema,
|
|
49
50
|
TransitionDealStateRequestSchema,
|
|
@@ -54,7 +55,7 @@ import {
|
|
|
54
55
|
UpdateListRequestSchema,
|
|
55
56
|
UpdateListStatusRequestSchema
|
|
56
57
|
} from './api-schemas'
|
|
57
|
-
import {
|
|
58
|
+
import { createBuildPlanSnapshotFromTemplate } from './build-templates'
|
|
58
59
|
import {
|
|
59
60
|
compileBusinessOntologyValidationIndex,
|
|
60
61
|
CRM_PIPELINE_CATALOG_ONTOLOGY_ID,
|
|
@@ -1168,17 +1169,32 @@ describe('PipelineStageSchema', () => {
|
|
|
1168
1169
|
// ---------------------------------------------------------------------------
|
|
1169
1170
|
|
|
1170
1171
|
describe('BuildPlanSnapshotSchema', () => {
|
|
1171
|
-
const validSnapshot =
|
|
1172
|
+
const validSnapshot = createBuildPlanSnapshotFromTemplate({
|
|
1173
|
+
id: 'tenant-template',
|
|
1174
|
+
label: 'Tenant Template',
|
|
1175
|
+
steps: [
|
|
1176
|
+
{
|
|
1177
|
+
id: 'source-companies',
|
|
1178
|
+
label: 'Companies found',
|
|
1179
|
+
primaryEntity: 'company',
|
|
1180
|
+
outputs: ['company'],
|
|
1181
|
+
stageKey: 'populated',
|
|
1182
|
+
dependencyMode: 'per-record-eligibility',
|
|
1183
|
+
actionKey: 'lead-gen.company.source',
|
|
1184
|
+
defaultBatchSize: 100,
|
|
1185
|
+
maxBatchSize: 250
|
|
1186
|
+
}
|
|
1187
|
+
]
|
|
1188
|
+
})
|
|
1172
1189
|
|
|
1173
1190
|
it('accepts a snapshot generated from a prospecting build template', () => {
|
|
1174
|
-
expect(validSnapshot).not.toBeNull()
|
|
1175
1191
|
expect(BuildPlanSnapshotSchema.safeParse(validSnapshot).success).toBe(true)
|
|
1176
1192
|
})
|
|
1177
1193
|
|
|
1178
1194
|
it('accepts custom non-empty step stage keys at the transport boundary', () => {
|
|
1179
1195
|
const result = BuildPlanSnapshotSchema.safeParse({
|
|
1180
1196
|
...validSnapshot,
|
|
1181
|
-
steps: [{ ...validSnapshot
|
|
1197
|
+
steps: [{ ...validSnapshot.steps[0], stageKey: 'made-up-stage' }]
|
|
1182
1198
|
})
|
|
1183
1199
|
|
|
1184
1200
|
expect(result.success).toBe(true)
|
|
@@ -1187,7 +1203,7 @@ describe('BuildPlanSnapshotSchema', () => {
|
|
|
1187
1203
|
it('accepts custom non-empty action keys at the transport boundary', () => {
|
|
1188
1204
|
const result = BuildPlanSnapshotSchema.safeParse({
|
|
1189
1205
|
...validSnapshot,
|
|
1190
|
-
steps: [{ ...validSnapshot
|
|
1206
|
+
steps: [{ ...validSnapshot.steps[0], actionKey: 'lead-gen.missing.action' }]
|
|
1191
1207
|
})
|
|
1192
1208
|
|
|
1193
1209
|
expect(result.success).toBe(true)
|
|
@@ -1293,8 +1309,8 @@ describe('CreateListRequestSchema', () => {
|
|
|
1293
1309
|
expect(result.success).toBe(true)
|
|
1294
1310
|
})
|
|
1295
1311
|
|
|
1296
|
-
it('
|
|
1297
|
-
expect(CreateListRequestSchema.safeParse({ name: 'X', buildTemplateId: 'not-a-template' }).success).toBe(
|
|
1312
|
+
it('accepts tenant-owned build template ids for API-layer membership validation', () => {
|
|
1313
|
+
expect(CreateListRequestSchema.safeParse({ name: 'X', buildTemplateId: 'not-a-template' }).success).toBe(true)
|
|
1298
1314
|
})
|
|
1299
1315
|
|
|
1300
1316
|
it('rejects an invalid status', () => {
|
|
@@ -1311,10 +1327,20 @@ describe('CreateListRequestSchema', () => {
|
|
|
1311
1327
|
name: 'SaaS List',
|
|
1312
1328
|
scrapingConfig: { vertical: 'SaaS' },
|
|
1313
1329
|
icp: { minReviewCount: 5 },
|
|
1314
|
-
pipelineConfig: { stages: [] }
|
|
1330
|
+
pipelineConfig: { stages: [], dataMode: 'mock' }
|
|
1315
1331
|
}).success
|
|
1316
1332
|
).toBe(true)
|
|
1317
1333
|
})
|
|
1334
|
+
|
|
1335
|
+
it('accepts create-time list-wide pipeline dataMode', () => {
|
|
1336
|
+
const result = CreateListRequestSchema.safeParse({
|
|
1337
|
+
name: 'DTC Demo',
|
|
1338
|
+
pipelineConfig: { dataMode: 'mock' }
|
|
1339
|
+
})
|
|
1340
|
+
|
|
1341
|
+
expect(result.success).toBe(true)
|
|
1342
|
+
if (result.success) expect(result.data.pipelineConfig?.dataMode).toBe('mock')
|
|
1343
|
+
})
|
|
1318
1344
|
})
|
|
1319
1345
|
|
|
1320
1346
|
// ---------------------------------------------------------------------------
|
|
@@ -1404,11 +1430,36 @@ describe('UpdateListConfigRequestSchema', () => {
|
|
|
1404
1430
|
expect(UpdateListConfigRequestSchema.safeParse({ pipelineConfig: { stages: [] } }).success).toBe(true)
|
|
1405
1431
|
})
|
|
1406
1432
|
|
|
1433
|
+
it('accepts a data-mode-only pipelineConfig patch', () => {
|
|
1434
|
+
const result = UpdateListConfigRequestSchema.safeParse({ pipelineConfig: { dataMode: 'live' } })
|
|
1435
|
+
|
|
1436
|
+
expect(result.success).toBe(true)
|
|
1437
|
+
if (result.success) expect(result.data.pipelineConfig?.dataMode).toBe('live')
|
|
1438
|
+
})
|
|
1439
|
+
|
|
1440
|
+
it('rejects invalid pipelineConfig dataMode values', () => {
|
|
1441
|
+
expect(UpdateListConfigRequestSchema.safeParse({ pipelineConfig: { dataMode: 'demo' } }).success).toBe(false)
|
|
1442
|
+
})
|
|
1443
|
+
|
|
1407
1444
|
it('rejects unknown fields (strict mode)', () => {
|
|
1408
1445
|
expect(UpdateListConfigRequestSchema.safeParse({ scrapingConfig: {}, bogus: true }).success).toBe(false)
|
|
1409
1446
|
})
|
|
1410
1447
|
})
|
|
1411
1448
|
|
|
1449
|
+
// ---------------------------------------------------------------------------
|
|
1450
|
+
// PipelineConfigSchema
|
|
1451
|
+
// ---------------------------------------------------------------------------
|
|
1452
|
+
|
|
1453
|
+
describe('PipelineConfigSchema', () => {
|
|
1454
|
+
it.each(['mock', 'live'])('accepts list-wide dataMode "%s"', (dataMode) => {
|
|
1455
|
+
expect(PipelineConfigSchema.safeParse({ dataMode }).success).toBe(true)
|
|
1456
|
+
})
|
|
1457
|
+
|
|
1458
|
+
it('keeps dataMode optional for legacy stored configs', () => {
|
|
1459
|
+
expect(PipelineConfigSchema.safeParse({ stages: [{ key: 'populated' }] }).success).toBe(true)
|
|
1460
|
+
})
|
|
1461
|
+
})
|
|
1462
|
+
|
|
1412
1463
|
// ---------------------------------------------------------------------------
|
|
1413
1464
|
// AddCompaniesToListRequestSchema / AddContactsToListRequestSchema
|
|
1414
1465
|
// ---------------------------------------------------------------------------
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { UuidSchema, NonEmptyStringSchema } from '../../platform/utils/validation'
|
|
3
3
|
import { CredentialRequirementSchema, RecordColumnConfigSchema } from '../../organization-model/domains/prospecting'
|
|
4
|
-
import { isProspectingBuildTemplateId } from './build-templates'
|
|
5
4
|
export { CrmPriorityBucketKeySchema, CrmPriorityBucketOverrideSchema, CrmPriorityOverrideSchema } from './crm-priority'
|
|
6
5
|
export type { CrmPriorityBucketOverride, CrmPriorityOverride, ResolvedCrmPriorityRuleConfig } from './crm-priority'
|
|
7
6
|
|
|
@@ -508,12 +507,15 @@ export const PipelineStageSchema = z.object({
|
|
|
508
507
|
order: z.number().int().optional()
|
|
509
508
|
})
|
|
510
509
|
|
|
510
|
+
export const DataModeSchema = z.enum(['mock', 'live'])
|
|
511
|
+
|
|
511
512
|
/**
|
|
512
513
|
* Pipeline presentation contract stored in `acq_lists.pipeline_config` jsonb.
|
|
513
514
|
* `stages[].key` validates against the catalog; the rest is presentation only.
|
|
514
515
|
*/
|
|
515
516
|
export const PipelineConfigSchema = z.object({
|
|
516
|
-
stages: z.array(PipelineStageSchema).optional()
|
|
517
|
+
stages: z.array(PipelineStageSchema).optional(),
|
|
518
|
+
dataMode: DataModeSchema.optional()
|
|
517
519
|
})
|
|
518
520
|
|
|
519
521
|
export const BuildPlanSnapshotStepSchema = z
|
|
@@ -584,9 +586,10 @@ export const AcqListMetadataSchema = z
|
|
|
584
586
|
})
|
|
585
587
|
.catchall(z.unknown())
|
|
586
588
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
589
|
+
// Build-template IDs are tenant-owned OM catalog entries. Published core only
|
|
590
|
+
// validates the transport shape; API/services validate membership against the
|
|
591
|
+
// tenant's resolved Organization Model before creating or changing snapshots.
|
|
592
|
+
export const ProspectingBuildTemplateIdSchema = z.string().trim().min(1).max(100)
|
|
590
593
|
|
|
591
594
|
// ---------------------------------------------------------------------------
|
|
592
595
|
// List telemetry / progress schemas
|
|
@@ -1360,6 +1363,7 @@ export const AcqListSchemas = {
|
|
|
1360
1363
|
ScrapingConfig: ScrapingConfigSchema,
|
|
1361
1364
|
IcpRubric: IcpRubricSchema,
|
|
1362
1365
|
PipelineConfig: PipelineConfigSchema,
|
|
1366
|
+
DataMode: DataModeSchema,
|
|
1363
1367
|
PipelineStage: PipelineStageSchema,
|
|
1364
1368
|
BuildPlanSnapshot: BuildPlanSnapshotSchema,
|
|
1365
1369
|
BuildPlanSnapshotStep: BuildPlanSnapshotStepSchema,
|
|
@@ -1459,6 +1463,7 @@ export type ListStatus = z.infer<typeof ListStatusSchema>
|
|
|
1459
1463
|
export type ScrapingConfig = z.infer<typeof ScrapingConfigSchema>
|
|
1460
1464
|
export type IcpRubric = z.infer<typeof IcpRubricSchema>
|
|
1461
1465
|
export type PipelineStage = z.infer<typeof PipelineStageSchema>
|
|
1466
|
+
export type DataMode = z.infer<typeof DataModeSchema>
|
|
1462
1467
|
export type PipelineConfig = z.infer<typeof PipelineConfigSchema>
|
|
1463
1468
|
export type BuildPlanSnapshotStep = z.infer<typeof BuildPlanSnapshotStepSchema>
|
|
1464
1469
|
export type BuildPlanSnapshot = z.infer<typeof BuildPlanSnapshotSchema>
|