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.
Files changed (65) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +2 -2
  2. package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +1 -1
  3. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
  4. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  5. package/dist/bundling-sources/shared_libs/integration-contracts/AGENTS.md +9 -0
  6. package/dist/bundling-sources/shared_libs/integration-contracts/README.md +3 -0
  7. package/dist/bundling-sources/shared_libs/integration-contracts/index.ts +2 -0
  8. package/dist/bundling-sources/shared_libs/monitors/analytics-install-guides.ts +77 -0
  9. package/dist/bundling-sources/shared_libs/monitors/billing/charges.ts +125 -0
  10. package/dist/bundling-sources/shared_libs/monitors/billing/policy-builders.ts +686 -0
  11. package/dist/bundling-sources/shared_libs/monitors/billing/pricing.ts +54 -0
  12. package/dist/bundling-sources/shared_libs/monitors/billing/public-pricing.ts +72 -0
  13. package/dist/bundling-sources/shared_libs/monitors/billing/session.ts +45 -0
  14. package/dist/bundling-sources/shared_libs/monitors/billing/state.ts +454 -0
  15. package/dist/bundling-sources/shared_libs/monitors/billing/tamradar.ts +260 -0
  16. package/dist/bundling-sources/shared_libs/monitors/billing/types.ts +215 -0
  17. package/dist/bundling-sources/shared_libs/monitors/callback-security.ts +333 -0
  18. package/dist/bundling-sources/shared_libs/monitors/cursor-page.ts +42 -0
  19. package/dist/bundling-sources/shared_libs/monitors/framework-managed-payload-fields.ts +71 -0
  20. package/dist/bundling-sources/shared_libs/monitors/generated/provider-monitor-callback-admission-targets.ts +16 -0
  21. package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +267 -0
  22. package/dist/bundling-sources/shared_libs/monitors/inbound-publish-key.ts +39 -0
  23. package/dist/bundling-sources/shared_libs/monitors/inbound-queue.ts +82 -0
  24. package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +473 -0
  25. package/dist/bundling-sources/shared_libs/monitors/org-monitor-limits.ts +1 -1
  26. package/dist/bundling-sources/shared_libs/monitors/package.json +29 -0
  27. package/dist/bundling-sources/shared_libs/monitors/provider-callback-verification.ts +1125 -0
  28. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-capability.ts +964 -0
  29. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-consumer-health.ts +56 -0
  30. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-errors.ts +131 -0
  31. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +527 -0
  32. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-legacy-aliases.ts +16 -0
  33. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-priority-execution.ts +56 -0
  34. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +28 -0
  35. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-runtime-read-eligibility.ts +38 -0
  36. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-status.ts +10 -0
  37. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-upstream-identity.ts +112 -0
  38. package/dist/bundling-sources/shared_libs/monitors/provider-webhook-monitor-runtime.ts +300 -0
  39. package/dist/bundling-sources/shared_libs/monitors/quickstart-prompts.ts +22 -0
  40. package/dist/bundling-sources/shared_libs/monitors/snitcher-install-validation.ts +93 -0
  41. package/dist/bundling-sources/shared_libs/monitors/snitcher-ip2company.ts +332 -0
  42. package/dist/bundling-sources/shared_libs/monitors/snitcher-radar.ts +221 -0
  43. package/dist/bundling-sources/shared_libs/monitors/starter-templates.ts +105 -0
  44. package/dist/bundling-sources/shared_libs/monitors/tracking-provider-guidance.ts +73 -0
  45. package/dist/bundling-sources/shared_libs/monitors/tracking-provider-integrations.ts +81 -0
  46. package/dist/bundling-sources/shared_libs/monitors/tsconfig.json +10 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/runtime-env.ts +6 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +1 -1
  49. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +1 -1
  50. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +302 -0
  51. package/dist/bundling-sources/shared_libs/plays/email-status.ts +1 -1
  52. package/dist/bundling-sources/shared_libs/plays/tool-category-descriptions.ts +2 -2
  53. package/dist/bundling-sources/shared_libs/runtime-env.ts +6 -0
  54. package/dist/bundling-sources/shared_libs/security/header-safe-env.ts +28 -0
  55. package/dist/bundling-sources/shared_libs/security/secret-redaction.ts +267 -0
  56. package/dist/cli/index.js +25 -14
  57. package/dist/cli/index.mjs +25 -14
  58. package/dist/index.d.mts +1 -1
  59. package/dist/index.d.ts +1 -1
  60. package/dist/index.js +3 -3
  61. package/dist/index.mjs +3 -3
  62. package/dist/install-integrity.json +45 -2
  63. package/package.json +3 -1
  64. /package/dist/bundling-sources/shared_libs/{integrations/bettercontact-execution-policy.ts → integration-contracts/bettercontact/execution-policy.ts} +0 -0
  65. /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
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "incremental": false,
5
+ "noEmit": true,
6
+ "types": ["node"]
7
+ },
8
+ "include": ["./**/*.ts"],
9
+ "exclude": ["node_modules"]
10
+ }
@@ -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
- * src/lib/integrations/execute-billing.ts. This class is the exact incident
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
- * `src/lib/integrations/execute-result-normalization.ts` BEFORE the response
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 src/lib/integrations/**). A coarse global
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 `src/lib/integrations/**`), not a single closed enum.
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 (src/lib/integrations/catalog-tags.ts). This map
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
+ }