deepline 0.3.85 → 0.3.87
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/bundling-sources/sdk/src/client.ts +2 -2
- package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +1 -1
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/integration-contracts/AGENTS.md +9 -0
- package/dist/bundling-sources/shared_libs/integration-contracts/README.md +3 -0
- package/dist/bundling-sources/shared_libs/integration-contracts/index.ts +2 -0
- package/dist/bundling-sources/shared_libs/monitors/analytics-install-guides.ts +77 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/charges.ts +125 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/policy-builders.ts +686 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/pricing.ts +54 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/public-pricing.ts +72 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/session.ts +45 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/state.ts +454 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/tamradar.ts +260 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/types.ts +215 -0
- package/dist/bundling-sources/shared_libs/monitors/callback-security.ts +333 -0
- package/dist/bundling-sources/shared_libs/monitors/cursor-page.ts +42 -0
- package/dist/bundling-sources/shared_libs/monitors/framework-managed-payload-fields.ts +71 -0
- package/dist/bundling-sources/shared_libs/monitors/generated/provider-monitor-callback-admission-targets.ts +16 -0
- package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +267 -0
- package/dist/bundling-sources/shared_libs/monitors/inbound-publish-key.ts +39 -0
- package/dist/bundling-sources/shared_libs/monitors/inbound-queue.ts +82 -0
- package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +473 -0
- package/dist/bundling-sources/shared_libs/monitors/org-monitor-limits.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/package.json +29 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-callback-verification.ts +1125 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-capability.ts +964 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-consumer-health.ts +56 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-errors.ts +131 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +527 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-legacy-aliases.ts +16 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-priority-execution.ts +56 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +28 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-runtime-read-eligibility.ts +38 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-status.ts +10 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-upstream-identity.ts +112 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-webhook-monitor-runtime.ts +300 -0
- package/dist/bundling-sources/shared_libs/monitors/quickstart-prompts.ts +22 -0
- package/dist/bundling-sources/shared_libs/monitors/snitcher-install-validation.ts +93 -0
- package/dist/bundling-sources/shared_libs/monitors/snitcher-ip2company.ts +332 -0
- package/dist/bundling-sources/shared_libs/monitors/snitcher-radar.ts +221 -0
- package/dist/bundling-sources/shared_libs/monitors/starter-templates.ts +105 -0
- package/dist/bundling-sources/shared_libs/monitors/tracking-provider-guidance.ts +73 -0
- package/dist/bundling-sources/shared_libs/monitors/tracking-provider-integrations.ts +81 -0
- package/dist/bundling-sources/shared_libs/monitors/tsconfig.json +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-env.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +302 -0
- package/dist/bundling-sources/shared_libs/plays/email-status.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/tool-category-descriptions.ts +2 -2
- package/dist/bundling-sources/shared_libs/runtime-env.ts +6 -0
- package/dist/bundling-sources/shared_libs/security/header-safe-env.ts +28 -0
- package/dist/bundling-sources/shared_libs/security/secret-redaction.ts +267 -0
- package/dist/cli/index.js +25 -14
- package/dist/cli/index.mjs +25 -14
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/index.mjs +3 -3
- package/dist/install-integrity.json +45 -2
- package/package.json +3 -1
- /package/dist/bundling-sources/shared_libs/{integrations/bettercontact-execution-policy.ts → integration-contracts/bettercontact/execution-policy.ts} +0 -0
- /package/dist/bundling-sources/shared_libs/{integrations/theirstack-execution-policy.ts → integration-contracts/theirstack/execution-policy.ts} +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ProviderMonitorPostDeployGuidance } from '@deepline/monitors/provider-monitor-capability';
|
|
2
|
+
|
|
3
|
+
// Client-safe provider setup metadata. Provider monitor definitions reference
|
|
4
|
+
// these exact objects, while the integrations dialog reads them directly.
|
|
5
|
+
export const VECTOR_POST_DEPLOY_GUIDANCE = {
|
|
6
|
+
title: 'Connect Vector to this monitor',
|
|
7
|
+
steps: [
|
|
8
|
+
'In Vector, open the segment named in this monitor setup.',
|
|
9
|
+
'Add a webhook endpoint using {{callback_url}}, then select contact.visited.',
|
|
10
|
+
'Enable the webhook action on the segment and send a Vector test event.',
|
|
11
|
+
],
|
|
12
|
+
event_type: 'contact.visited',
|
|
13
|
+
documentation: [
|
|
14
|
+
{
|
|
15
|
+
label: 'Vector SiteID action integration guide',
|
|
16
|
+
url: 'https://learn.vector.co/articles/2260541690-building-an-action-integration-for-siteid',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
payload_example: {
|
|
20
|
+
type: 'contact.visited',
|
|
21
|
+
externalId: 'contact_123',
|
|
22
|
+
companyDomain: 'example.com',
|
|
23
|
+
},
|
|
24
|
+
} as const satisfies ProviderMonitorPostDeployGuidance;
|
|
25
|
+
|
|
26
|
+
export const RB2B_POST_DEPLOY_GUIDANCE = {
|
|
27
|
+
title: 'Connect RB2B to this monitor',
|
|
28
|
+
steps: [
|
|
29
|
+
'In RB2B, open Integrations and choose Webhook.',
|
|
30
|
+
'Paste {{callback_url}} as the single webhook URL and save.',
|
|
31
|
+
"Use RB2B's Send a Test Event action to verify the connection.",
|
|
32
|
+
],
|
|
33
|
+
documentation: [
|
|
34
|
+
{
|
|
35
|
+
label: 'RB2B webhook setup guide',
|
|
36
|
+
url: 'https://support.rb2b.com/en/articles/8976614-setup-guide-webhook',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
payload_example: {
|
|
40
|
+
'LinkedIn URL': 'https://www.linkedin.com/in/example',
|
|
41
|
+
Website: 'https://example.com',
|
|
42
|
+
},
|
|
43
|
+
} as const satisfies ProviderMonitorPostDeployGuidance;
|
|
44
|
+
|
|
45
|
+
export const SNITCHER_POST_DEPLOY_GUIDANCE = {
|
|
46
|
+
title: 'Install Deepline Analytics',
|
|
47
|
+
steps: [
|
|
48
|
+
'Create the tracker code in Deepline, then paste it in your site’s document head.',
|
|
49
|
+
'Confirm cookie consent before persistent tracking when your privacy policy requires it.',
|
|
50
|
+
],
|
|
51
|
+
documentation: [
|
|
52
|
+
{
|
|
53
|
+
label: 'Deepline Analytics setup guide',
|
|
54
|
+
url: 'https://docs.snitcher.com/powered-by-snitcher/radar/installation',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
payload_example: {
|
|
58
|
+
session_uuid: '00000000-0000-4000-8000-000000000001',
|
|
59
|
+
start_time: '2026-01-01T00:00:00.000Z',
|
|
60
|
+
last_activity: '2026-01-01T00:01:00.000Z',
|
|
61
|
+
engagement_time: 60,
|
|
62
|
+
user_properties: { $uuid: 'deepline-test-visitor' },
|
|
63
|
+
events: [
|
|
64
|
+
{ event_name: '$pageview', created_at: '2026-01-01T00:00:00.000Z' },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
} as const satisfies ProviderMonitorPostDeployGuidance;
|
|
68
|
+
|
|
69
|
+
export const TRACKING_PROVIDER_POST_DEPLOY_GUIDANCE = {
|
|
70
|
+
vector: VECTOR_POST_DEPLOY_GUIDANCE,
|
|
71
|
+
rb2b: RB2B_POST_DEPLOY_GUIDANCE,
|
|
72
|
+
snitcher: SNITCHER_POST_DEPLOY_GUIDANCE,
|
|
73
|
+
} as const;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { TRACKING_PROVIDER_POST_DEPLOY_GUIDANCE } from '@deepline/monitors/tracking-provider-guidance';
|
|
2
|
+
import {
|
|
3
|
+
ANALYTICS_INTEGRATION_SLUG,
|
|
4
|
+
ANALYTICS_PUBLIC_TOOL,
|
|
5
|
+
} from '@/lib/analytics/public';
|
|
6
|
+
|
|
7
|
+
export const TRACKING_PROVIDER_INTEGRATIONS = {
|
|
8
|
+
vector: {
|
|
9
|
+
provider: 'vector',
|
|
10
|
+
displayName: 'Vector',
|
|
11
|
+
tool: 'vector.visitor_events',
|
|
12
|
+
monitorKey: 'vector-high-intent-visitors',
|
|
13
|
+
},
|
|
14
|
+
rb2b: {
|
|
15
|
+
provider: 'rb2b',
|
|
16
|
+
displayName: 'RB2B',
|
|
17
|
+
tool: 'rb2b.visitor_events',
|
|
18
|
+
monitorKey: 'rb2b-website-visitors',
|
|
19
|
+
},
|
|
20
|
+
[ANALYTICS_INTEGRATION_SLUG]: {
|
|
21
|
+
provider: 'snitcher',
|
|
22
|
+
displayName: 'Deepline Analytics',
|
|
23
|
+
// Used only by the server-side monitor adapter to resolve upstream wiring.
|
|
24
|
+
tool: 'snitcher.website_sessions',
|
|
25
|
+
publicTool: ANALYTICS_PUBLIC_TOOL,
|
|
26
|
+
monitorKey: 'snitcher-website-sessions',
|
|
27
|
+
},
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export type TrackingProviderIntegration =
|
|
31
|
+
(typeof TRACKING_PROVIDER_INTEGRATIONS)[keyof typeof TRACKING_PROVIDER_INTEGRATIONS] & {
|
|
32
|
+
docsUrl: string;
|
|
33
|
+
docsLabel: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function getTrackingProviderIntegration(
|
|
37
|
+
provider: string,
|
|
38
|
+
): TrackingProviderIntegration | null {
|
|
39
|
+
const integration =
|
|
40
|
+
TRACKING_PROVIDER_INTEGRATIONS[
|
|
41
|
+
provider as keyof typeof TRACKING_PROVIDER_INTEGRATIONS
|
|
42
|
+
];
|
|
43
|
+
if (!integration) return null;
|
|
44
|
+
const docs =
|
|
45
|
+
TRACKING_PROVIDER_POST_DEPLOY_GUIDANCE[
|
|
46
|
+
integration.provider as keyof typeof TRACKING_PROVIDER_POST_DEPLOY_GUIDANCE
|
|
47
|
+
]?.documentation[0];
|
|
48
|
+
if (!docs) return null;
|
|
49
|
+
return { ...integration, docsUrl: docs.url, docsLabel: docs.label };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function trackingProviderWebhookSetup(
|
|
53
|
+
provider: string,
|
|
54
|
+
webhookEndpoint: string,
|
|
55
|
+
): {
|
|
56
|
+
webhook_endpoint: string;
|
|
57
|
+
event_type?: 'contact.visited';
|
|
58
|
+
instructions: string[];
|
|
59
|
+
} | null {
|
|
60
|
+
const integration = getTrackingProviderIntegration(
|
|
61
|
+
provider === 'snitcher' ? ANALYTICS_INTEGRATION_SLUG : provider,
|
|
62
|
+
);
|
|
63
|
+
const guidance = integration
|
|
64
|
+
? TRACKING_PROVIDER_POST_DEPLOY_GUIDANCE[
|
|
65
|
+
integration.provider as keyof typeof TRACKING_PROVIDER_POST_DEPLOY_GUIDANCE
|
|
66
|
+
]
|
|
67
|
+
: null;
|
|
68
|
+
if (!guidance) return null;
|
|
69
|
+
return {
|
|
70
|
+
webhook_endpoint: webhookEndpoint,
|
|
71
|
+
...('event_type' in guidance && guidance.event_type === 'contact.visited'
|
|
72
|
+
? { event_type: guidance.event_type }
|
|
73
|
+
: {}),
|
|
74
|
+
// The dialog shows the URL in a dedicated copy target, so retain its
|
|
75
|
+
// compact `webhook_endpoint` wording while deriving every step from the
|
|
76
|
+
// same metadata used by CLI/API guidance.
|
|
77
|
+
instructions: guidance.steps.map((step) =>
|
|
78
|
+
step.replaceAll('{{callback_url}}', 'webhook_endpoint'),
|
|
79
|
+
),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -53,3 +53,9 @@ export function getRuntimeEnvInfo() {
|
|
|
53
53
|
node_env: process.env.NODE_ENV ?? null,
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
/** Test seams must never be reachable from a Vercel production deployment. */
|
|
58
|
+
export function isTestOnlyRuntime(): boolean {
|
|
59
|
+
if (process.env.VERCEL_ENV === 'production') return false;
|
|
60
|
+
return process.env.VERCEL_ENV === 'preview' || getRuntimeEnv() !== 'prod';
|
|
61
|
+
}
|
|
@@ -186,7 +186,7 @@ function isInsufficientCreditsBilling(
|
|
|
186
186
|
* credit/cap denial: a funded org is temporarily blocked because our billing
|
|
187
187
|
* store hiccuped, so it must be repairable on the next run rather than cached as
|
|
188
188
|
* a terminal failure. See `billingUnavailableResponse` in
|
|
189
|
-
*
|
|
189
|
+
* packages/integrations/execute-billing.ts. This class is the exact incident
|
|
190
190
|
* poison closed by issues #2561/#2706: without this carve-out a billing-plane
|
|
191
191
|
* outage classifies every affected tool call as `hard_billing_error` ->
|
|
192
192
|
* `terminal` and the next run replays the cached failure instead of retrying.
|
|
@@ -206,7 +206,7 @@ function adaptV2ExecuteResponseToToolResult(data: Record<string, unknown>): {
|
|
|
206
206
|
*
|
|
207
207
|
* Boundary note: provider-specific redaction (wiza/apify/bettercontact billing
|
|
208
208
|
* fields) happens server-side in
|
|
209
|
-
* `
|
|
209
|
+
* `packages/integrations/execute-result-normalization.ts` BEFORE the response
|
|
210
210
|
* leaves the API. This module only ever sees the already-redacted public
|
|
211
211
|
* response — do not move redaction here, it would leak provider internals
|
|
212
212
|
* into the shared runtime bundles.
|
|
@@ -276,6 +276,308 @@ export function normalizeAdmittedSqlListenerWhere(
|
|
|
276
276
|
}
|
|
277
277
|
return normalized;
|
|
278
278
|
}
|
|
279
|
+
export type PlaySqlListenerValidationIssue = {
|
|
280
|
+
severity: 'error' | 'warning';
|
|
281
|
+
code: string;
|
|
282
|
+
path: string;
|
|
283
|
+
message: string;
|
|
284
|
+
expected?: unknown;
|
|
285
|
+
received?: unknown;
|
|
286
|
+
suggestion?: string;
|
|
287
|
+
entity_type?:
|
|
288
|
+
| 'monitor'
|
|
289
|
+
| 'action'
|
|
290
|
+
| 'trigger'
|
|
291
|
+
| 'bundle'
|
|
292
|
+
| 'play'
|
|
293
|
+
| 'listener'
|
|
294
|
+
| 'table'
|
|
295
|
+
| 'tool'
|
|
296
|
+
| 'workflow';
|
|
297
|
+
entity_key?: string;
|
|
298
|
+
docs_ref?: string;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
function sqlListenerIssue(
|
|
302
|
+
issue: Omit<PlaySqlListenerValidationIssue, 'severity'> & {
|
|
303
|
+
severity?: PlaySqlListenerValidationIssue['severity'];
|
|
304
|
+
},
|
|
305
|
+
): PlaySqlListenerValidationIssue {
|
|
306
|
+
return { severity: issue.severity ?? 'error', ...issue };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const PLAY_SQL_LISTENER_OPERATIONS = new Set<string>([
|
|
310
|
+
'INSERT',
|
|
311
|
+
'UPDATE',
|
|
312
|
+
'DELETE',
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
function validateAdmittedSqlListenerWhere(
|
|
316
|
+
input: { where: unknown; path: string; listenerKey: string },
|
|
317
|
+
issues: PlaySqlListenerValidationIssue[],
|
|
318
|
+
): void {
|
|
319
|
+
if (input.where === undefined) return;
|
|
320
|
+
if (!isPlainRecord(input.where)) {
|
|
321
|
+
issues.push(
|
|
322
|
+
sqlListenerIssue({
|
|
323
|
+
code: 'invalid_sql_listener_filter_shape',
|
|
324
|
+
path: input.path,
|
|
325
|
+
message:
|
|
326
|
+
'SQL listener where must be a literal object with before and/or after filters.',
|
|
327
|
+
expected:
|
|
328
|
+
'{ before?: Record<string, operators>, after?: Record<string, operators> }',
|
|
329
|
+
received: input.where,
|
|
330
|
+
entity_type: 'listener',
|
|
331
|
+
entity_key: input.listenerKey,
|
|
332
|
+
docs_ref: 'docs/monitor-sdk-architecture-plan.html#v2',
|
|
333
|
+
}),
|
|
334
|
+
);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
for (const [scope, scopeValue] of Object.entries(input.where)) {
|
|
338
|
+
const scopePath = `${input.path}.${scope}`;
|
|
339
|
+
if (scope !== 'before' && scope !== 'after') {
|
|
340
|
+
issues.push(
|
|
341
|
+
sqlListenerIssue({
|
|
342
|
+
code: 'unsupported_sql_listener_filter_scope',
|
|
343
|
+
path: scopePath,
|
|
344
|
+
message: 'SQL listener filters only support before and after scopes.',
|
|
345
|
+
expected: ['before', 'after'],
|
|
346
|
+
received: scope,
|
|
347
|
+
entity_type: 'listener',
|
|
348
|
+
entity_key: input.listenerKey,
|
|
349
|
+
docs_ref: 'docs/monitor-sdk-architecture-plan.html#v2',
|
|
350
|
+
}),
|
|
351
|
+
);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (!isPlainRecord(scopeValue)) {
|
|
355
|
+
issues.push(
|
|
356
|
+
sqlListenerIssue({
|
|
357
|
+
code: 'invalid_sql_listener_filter_scope',
|
|
358
|
+
path: scopePath,
|
|
359
|
+
message: `SQL listener where.${scope} must be an object keyed by top-level row field.`,
|
|
360
|
+
expected: 'Record<field, operators>',
|
|
361
|
+
received: scopeValue,
|
|
362
|
+
entity_type: 'listener',
|
|
363
|
+
entity_key: input.listenerKey,
|
|
364
|
+
docs_ref: 'docs/monitor-sdk-architecture-plan.html#v2',
|
|
365
|
+
}),
|
|
366
|
+
);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
for (const [field, operators] of Object.entries(scopeValue)) {
|
|
370
|
+
const fieldPath = `${scopePath}.${field}`;
|
|
371
|
+
if (!PLAY_SQL_LISTENER_FIELD_PATTERN.test(field)) {
|
|
372
|
+
issues.push(
|
|
373
|
+
sqlListenerIssue({
|
|
374
|
+
code: 'invalid_sql_listener_filter_field',
|
|
375
|
+
path: fieldPath,
|
|
376
|
+
message:
|
|
377
|
+
'SQL listener filters only support top-level row fields with plain identifier names.',
|
|
378
|
+
expected: PLAY_SQL_LISTENER_FIELD_PATTERN.source,
|
|
379
|
+
received: field,
|
|
380
|
+
suggestion:
|
|
381
|
+
'Use a top-level column key such as "country" instead of a path expression.',
|
|
382
|
+
entity_type: 'listener',
|
|
383
|
+
entity_key: input.listenerKey,
|
|
384
|
+
docs_ref: 'docs/monitor-sdk-architecture-plan.html#v2',
|
|
385
|
+
}),
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
if (!isPlainRecord(operators)) {
|
|
389
|
+
issues.push(
|
|
390
|
+
sqlListenerIssue({
|
|
391
|
+
code: 'invalid_sql_listener_filter_condition',
|
|
392
|
+
path: fieldPath,
|
|
393
|
+
message:
|
|
394
|
+
'SQL listener field filters must be an object keyed by a supported operator, e.g. { eq: <value> } — got a bare value.',
|
|
395
|
+
expected: PLAY_SQL_LISTENER_WHERE_OPERATORS,
|
|
396
|
+
received: operators,
|
|
397
|
+
entity_type: 'listener',
|
|
398
|
+
entity_key: input.listenerKey,
|
|
399
|
+
docs_ref: 'docs/monitor-sdk-architecture-plan.html#v2',
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (Object.keys(operators).length === 0) {
|
|
405
|
+
issues.push(
|
|
406
|
+
sqlListenerIssue({
|
|
407
|
+
code: 'empty_sql_listener_filter_condition',
|
|
408
|
+
path: fieldPath,
|
|
409
|
+
message:
|
|
410
|
+
'SQL listener field filters must contain at least one operator.',
|
|
411
|
+
expected: PLAY_SQL_LISTENER_WHERE_OPERATORS,
|
|
412
|
+
received: operators,
|
|
413
|
+
entity_type: 'listener',
|
|
414
|
+
entity_key: input.listenerKey,
|
|
415
|
+
docs_ref: 'docs/monitor-sdk-architecture-plan.html#v2',
|
|
416
|
+
}),
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
for (const [operator, value] of Object.entries(operators)) {
|
|
420
|
+
const operatorPath = `${fieldPath}.${operator}`;
|
|
421
|
+
if (!PLAY_SQL_LISTENER_WHERE_OPERATOR_SET.has(operator)) {
|
|
422
|
+
issues.push(
|
|
423
|
+
sqlListenerIssue({
|
|
424
|
+
code: 'unsupported_sql_listener_filter_operator',
|
|
425
|
+
path: operatorPath,
|
|
426
|
+
message: `SQL listener filter operator "${operator}" is not supported.`,
|
|
427
|
+
expected: PLAY_SQL_LISTENER_WHERE_OPERATORS,
|
|
428
|
+
received: operator,
|
|
429
|
+
suggestion: `Use ${PLAY_SQL_LISTENER_WHERE_OPERATORS.slice(0, -1).join(', ')}, or ${PLAY_SQL_LISTENER_WHERE_OPERATORS.at(-1)}.`,
|
|
430
|
+
entity_type: 'listener',
|
|
431
|
+
entity_key: input.listenerKey,
|
|
432
|
+
docs_ref: 'docs/monitor-sdk-architecture-plan.html#v2',
|
|
433
|
+
}),
|
|
434
|
+
);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const valid =
|
|
438
|
+
((operator === 'in' || operator === 'notIn') &&
|
|
439
|
+
Array.isArray(value) &&
|
|
440
|
+
value.length > 0 &&
|
|
441
|
+
value.every(isPlaySqlListenerFilterScalar)) ||
|
|
442
|
+
((operator === 'isNull' || operator === 'isNotNull') &&
|
|
443
|
+
value === true) ||
|
|
444
|
+
(operator === 'ilike' &&
|
|
445
|
+
typeof value === 'string' &&
|
|
446
|
+
value.length > 0) ||
|
|
447
|
+
((operator === 'eq' || operator === 'neq') &&
|
|
448
|
+
isPlaySqlListenerFilterScalar(value));
|
|
449
|
+
if (!valid) {
|
|
450
|
+
issues.push(
|
|
451
|
+
sqlListenerIssue({
|
|
452
|
+
code: 'invalid_sql_listener_filter_value',
|
|
453
|
+
path: operatorPath,
|
|
454
|
+
message: `SQL listener filter operator "${operator}" has an invalid value.`,
|
|
455
|
+
received: value,
|
|
456
|
+
entity_type: 'listener',
|
|
457
|
+
entity_key: input.listenerKey,
|
|
458
|
+
docs_ref: 'docs/monitor-sdk-architecture-plan.html#v2',
|
|
459
|
+
}),
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** Validate only a stored binding snapshot that already crossed admission. */
|
|
468
|
+
export function validateAdmittedSqlListenerBindings(
|
|
469
|
+
bindings: PlayAuthoringAstBindings | null | undefined,
|
|
470
|
+
input: { playName?: string; pathPrefix?: string } = {},
|
|
471
|
+
): PlaySqlListenerValidationIssue[] {
|
|
472
|
+
const issues: PlaySqlListenerValidationIssue[] = [];
|
|
473
|
+
const listeners = bindings?.sqlListeners;
|
|
474
|
+
if (!listeners) return issues;
|
|
475
|
+
const seen = new Set<string>();
|
|
476
|
+
listeners.forEach((listener, index) => {
|
|
477
|
+
const path = `${input.pathPrefix ?? 'sqlListeners'}[${index}]`;
|
|
478
|
+
const listenerKey = input.playName
|
|
479
|
+
? `${input.playName}.${listener.id}`
|
|
480
|
+
: listener.id;
|
|
481
|
+
if (!listener.id || !PLAY_SQL_LISTENER_ID_PATTERN.test(listener.id)) {
|
|
482
|
+
issues.push(
|
|
483
|
+
sqlListenerIssue({
|
|
484
|
+
code: 'invalid_sql_listener_id',
|
|
485
|
+
path: `${path}.id`,
|
|
486
|
+
message:
|
|
487
|
+
'SQL listener id must start with a letter and contain only letters, numbers, underscores, or hyphens.',
|
|
488
|
+
expected: PLAY_SQL_LISTENER_ID_PATTERN.source,
|
|
489
|
+
received: listener.id,
|
|
490
|
+
entity_type: 'listener',
|
|
491
|
+
entity_key: listenerKey,
|
|
492
|
+
}),
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
if (seen.has(listener.id)) {
|
|
496
|
+
issues.push(
|
|
497
|
+
sqlListenerIssue({
|
|
498
|
+
code: 'duplicate_sql_listener_id',
|
|
499
|
+
path: `${path}.id`,
|
|
500
|
+
message: `SQL listener id "${listener.id}" is already used in this play.`,
|
|
501
|
+
received: listener.id,
|
|
502
|
+
entity_type: 'listener',
|
|
503
|
+
entity_key: listenerKey,
|
|
504
|
+
}),
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
seen.add(listener.id);
|
|
508
|
+
if (!listener.tool || !PLAY_SQL_LISTENER_TOOL_PATTERN.test(listener.tool)) {
|
|
509
|
+
issues.push(
|
|
510
|
+
sqlListenerIssue({
|
|
511
|
+
code: listener.tool
|
|
512
|
+
? 'invalid_sql_listener_tool'
|
|
513
|
+
: 'missing_sql_listener_tool',
|
|
514
|
+
path: `${path}.tool`,
|
|
515
|
+
message: 'SQL listener tool must use provider.tool syntax.',
|
|
516
|
+
expected: PLAY_SQL_LISTENER_TOOL_PATTERN.source,
|
|
517
|
+
received: listener.tool,
|
|
518
|
+
entity_type: 'tool',
|
|
519
|
+
entity_key: listener.tool || listenerKey,
|
|
520
|
+
}),
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
if (
|
|
524
|
+
!listener.stream ||
|
|
525
|
+
!PLAY_SQL_LISTENER_ID_PATTERN.test(listener.stream)
|
|
526
|
+
) {
|
|
527
|
+
issues.push(
|
|
528
|
+
sqlListenerIssue({
|
|
529
|
+
code: listener.stream
|
|
530
|
+
? 'invalid_sql_listener_stream'
|
|
531
|
+
: 'missing_sql_listener_stream',
|
|
532
|
+
path: `${path}.stream`,
|
|
533
|
+
message:
|
|
534
|
+
'SQL listener stream keys must start with a letter and contain only letters, numbers, underscores, or hyphens.',
|
|
535
|
+
expected: PLAY_SQL_LISTENER_ID_PATTERN.source,
|
|
536
|
+
received: listener.stream,
|
|
537
|
+
entity_type: 'listener',
|
|
538
|
+
entity_key: listenerKey,
|
|
539
|
+
}),
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
validateAdmittedSqlListenerWhere(
|
|
543
|
+
{ where: listener.where, path: `${path}.where`, listenerKey },
|
|
544
|
+
issues,
|
|
545
|
+
);
|
|
546
|
+
if (
|
|
547
|
+
!Array.isArray(listener.operations) ||
|
|
548
|
+
listener.operations.length === 0
|
|
549
|
+
) {
|
|
550
|
+
issues.push(
|
|
551
|
+
sqlListenerIssue({
|
|
552
|
+
code: 'empty_sql_listener_operations',
|
|
553
|
+
path: `${path}.operations`,
|
|
554
|
+
message: 'SQL listener operations must not be empty.',
|
|
555
|
+
expected: [...PLAY_SQL_LISTENER_OPERATIONS],
|
|
556
|
+
received: listener.operations,
|
|
557
|
+
entity_type: 'listener',
|
|
558
|
+
entity_key: listenerKey,
|
|
559
|
+
}),
|
|
560
|
+
);
|
|
561
|
+
} else if (
|
|
562
|
+
listener.operations.some(
|
|
563
|
+
(operation) => !PLAY_SQL_LISTENER_OPERATIONS.has(operation),
|
|
564
|
+
)
|
|
565
|
+
) {
|
|
566
|
+
issues.push(
|
|
567
|
+
sqlListenerIssue({
|
|
568
|
+
code: 'invalid_sql_listener_operation',
|
|
569
|
+
path: `${path}.operations`,
|
|
570
|
+
message: 'SQL listener operations must be INSERT, UPDATE, or DELETE.',
|
|
571
|
+
expected: [...PLAY_SQL_LISTENER_OPERATIONS],
|
|
572
|
+
received: listener.operations,
|
|
573
|
+
entity_type: 'listener',
|
|
574
|
+
entity_key: listenerKey,
|
|
575
|
+
}),
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
return issues;
|
|
580
|
+
}
|
|
279
581
|
export type PlaySqlListenerDeclaration = {
|
|
280
582
|
id: string;
|
|
281
583
|
tool: string;
|
|
@@ -91,7 +91,7 @@ export type EmailStatusBuildInput = {
|
|
|
91
91
|
|
|
92
92
|
// There is intentionally NO shared default status map. Each provider must
|
|
93
93
|
// declare its own `statusMap` / `rules` for the raw status strings it emits
|
|
94
|
-
// (see the provider registries under
|
|
94
|
+
// (see the provider registries under packages/integrations/**). A coarse global
|
|
95
95
|
// map silently coerced provider-specific strings ("deliverable", "accept_all",
|
|
96
96
|
// etc.) into canonical verdicts and let validators depend on guesses instead of
|
|
97
97
|
// documented behavior. With it gone, an unmapped raw status falls through to
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// One-line definitions for the tool categories surfaced by `deepline tools list`.
|
|
2
2
|
//
|
|
3
3
|
// Tool categories are free-form strings that each provider tool declares in the
|
|
4
|
-
// generated catalog (see `
|
|
4
|
+
// generated catalog (see `packages/integrations/**`), not a single closed enum.
|
|
5
5
|
// The canonical set of well-known category slugs lives next to this file in
|
|
6
6
|
// `DEEPLINE_TOOL_CATEGORIES` (bootstrap-routes.ts) and in the richer
|
|
7
|
-
// `IntegrationCatalogTag` enum (
|
|
7
|
+
// `IntegrationCatalogTag` enum (packages/integrations/catalog-tags.ts). This map
|
|
8
8
|
// MUST track the categories that actually appear on live tools. The CLI derives
|
|
9
9
|
// the category enumeration from the live catalog, so a new category slug shows
|
|
10
10
|
// up automatically; only its human-readable definition comes from here. When a
|
|
@@ -53,3 +53,9 @@ export function getRuntimeEnvInfo() {
|
|
|
53
53
|
node_env: process.env.NODE_ENV ?? null,
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
/** Test seams must never be reachable from a Vercel production deployment. */
|
|
58
|
+
export function isTestOnlyRuntime(): boolean {
|
|
59
|
+
if (process.env.VERCEL_ENV === 'production') return false;
|
|
60
|
+
return process.env.VERCEL_ENV === 'preview' || getRuntimeEnv() !== 'prod';
|
|
61
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guards for env-derived secrets that are sent as HTTP header values.
|
|
3
|
+
*
|
|
4
|
+
* fetch() validates header values and may echo invalid raw values in its
|
|
5
|
+
* error. Validate secrets before constructing headers so failures identify
|
|
6
|
+
* the environment variable without leaking its contents.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const HEADER_UNSAFE_CHAR_PATTERN = /[^\t\u0020-\u007e\u0080-\u00ff]/;
|
|
10
|
+
|
|
11
|
+
export function isHeaderSafeValue(value: string): boolean {
|
|
12
|
+
return !HEADER_UNSAFE_CHAR_PATTERN.test(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function assertHeaderSafeEnvValue(input: {
|
|
16
|
+
envVar: string;
|
|
17
|
+
value: string;
|
|
18
|
+
}): string {
|
|
19
|
+
if (!isHeaderSafeValue(input.value)) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Environment variable ${input.envVar} contains a character that is invalid in an HTTP header value ` +
|
|
22
|
+
`(typically an embedded newline from a multi-line paste). ` +
|
|
23
|
+
`Re-set ${input.envVar} as a single-line value. ` +
|
|
24
|
+
`The raw value is not echoed to avoid leaking a secret.`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return input.value;
|
|
28
|
+
}
|