deepline 0.2.49 → 0.2.51

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.
@@ -0,0 +1,61 @@
1
+ // Compile-time safety gate for user-authored play code (enrich `extract_js`,
2
+ // `run_if_js`, and `run_javascript` step `code`).
3
+ //
4
+ // Plays replay deterministically under Worker Loader, and the step code runs
5
+ // with provider data in scope. So user code may NOT be non-deterministic
6
+ // (Math.random, Date.now, ...) — replay would diverge — and may NOT reach out
7
+ // of the sandbox (fetch, require, process, ...). We reject those at compile
8
+ // time with a clear, named error (fail loud, per the repo non-negotiables).
9
+ //
10
+ // This is a fast static scan, not a security boundary on its own: the play
11
+ // runtime is the real boundary (it executes step code in an isolate that does
12
+ // not expose these globals). The scan exists to fail authoring early with a
13
+ // readable message instead of producing a play that silently misbehaves at run
14
+ // time. The `(?<!\.)` guards keep it from flagging harmless property access on
15
+ // user objects (e.g. `row.process_date`, `data.fetch`).
16
+
17
+ type ForbiddenRule = { readonly pattern: RegExp; readonly reason: string };
18
+
19
+ const FORBIDDEN: readonly ForbiddenRule[] = [
20
+ // Non-deterministic — breaks replay.
21
+ { pattern: /\bMath\s*\.\s*random\b/, reason: 'Math.random()' },
22
+ { pattern: /\bDate\s*\.\s*now\b/, reason: 'Date.now()' },
23
+ { pattern: /\bnew\s+Date\s*\(\s*\)/, reason: 'new Date() with no argument' },
24
+ { pattern: /\bperformance\s*\.\s*now\b/, reason: 'performance.now()' },
25
+ {
26
+ pattern: /\bcrypto\s*\.\s*(?:randomUUID|getRandomValues)\b/,
27
+ reason: 'crypto random',
28
+ },
29
+ // Sandbox escape / I/O.
30
+ { pattern: /(?<!\.)\bfetch\s*\(/, reason: 'fetch()' },
31
+ { pattern: /(?<!\.)\bimport\s*\(/, reason: 'dynamic import()' },
32
+ { pattern: /(?<!\.)\brequire\s*\(/, reason: 'require()' },
33
+ { pattern: /(?<!\.)\beval\s*\(/, reason: 'eval()' },
34
+ {
35
+ pattern: /(?<!\.)\bnew\s+Function\b|(?<!\.)\bFunction\s*\(/,
36
+ reason: 'the Function constructor',
37
+ },
38
+ { pattern: /(?<!\.)\bprocess\b/, reason: 'process' },
39
+ { pattern: /(?<!\.)\bglobalThis\b/, reason: 'globalThis' },
40
+ { pattern: /(?<!\.)\b(?:window|self)\b/, reason: 'window/self' },
41
+ { pattern: /(?<!\.)\bXMLHttpRequest\b/, reason: 'XMLHttpRequest' },
42
+ { pattern: /(?<!\.)\bWebAssembly\b/, reason: 'WebAssembly' },
43
+ ];
44
+
45
+ /**
46
+ * Throw if `code` references a forbidden (non-deterministic or sandbox-escaping)
47
+ * construct. `label` describes where the code came from for the error message,
48
+ * e.g. `extract_js for "email"` or `run_javascript step "enrich"`.
49
+ */
50
+ export function assertUserCodeIsSafe(code: string, label: string): void {
51
+ if (typeof code !== 'string' || !code.trim()) return;
52
+ for (const { pattern, reason } of FORBIDDEN) {
53
+ if (pattern.test(code)) {
54
+ throw new Error(
55
+ `${label} uses ${reason}, which is not allowed in play code: it ` +
56
+ `breaks deterministic replay or escapes the sandbox. Remove it and ` +
57
+ `compute the value from the row/result instead.`,
58
+ );
59
+ }
60
+ }
61
+ }
package/dist/cli/index.js CHANGED
@@ -1044,7 +1044,7 @@ var SDK_RELEASE = {
1044
1044
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
1045
1045
  // exposed storage-dependent synchronous access. This deliberate minor
1046
1046
  // release keeps lazy paging semantics independent of row residency.
1047
- version: "0.2.49",
1047
+ version: "0.2.51",
1048
1048
  contracts: {
1049
1049
  api: {
1050
1050
  name: "sdk-http-api",
@@ -6034,6 +6034,7 @@ var DeeplineClient = class {
6034
6034
  params.set("limit", String(options.limit));
6035
6035
  if (options?.cursor) params.set("cursor", options.cursor);
6036
6036
  if (options?.compact) params.set("compact", "true");
6037
+ if (options?.includeConsumers) params.set("include_consumers", "true");
6037
6038
  const query = params.toString();
6038
6039
  const suffix = query ? `?${query}` : "";
6039
6040
  return this.http.request(
@@ -6055,7 +6056,7 @@ var DeeplineClient = class {
6055
6056
  method: "POST",
6056
6057
  body: {
6057
6058
  payload,
6058
- ...options?.validationOnly ? { mode: "validation_only" } : {}
6059
+ ...options?.validationOnly ? { mode: "validation_only" } : options?.dispatch ? { mode: "dispatch" } : {}
6059
6060
  }
6060
6061
  }
6061
6062
  );
@@ -16170,6 +16171,7 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
16170
16171
  " csv<T = Record<string, unknown>>(path: string | CsvInput<T & object>, options?: CsvOptions): Promise<PlayDataset<T>>;",
16171
16172
  " dataset<TSource extends PlayDatasetInput<object>>(key: string, items: TSource): DatasetBuilder<PlayDatasetRow<TSource> & object, PlayDatasetRow<TSource> & object>;",
16172
16173
  " map<TSource extends PlayDatasetInput<object>>(key: string, items: TSource, options?: DatasetDefinitionOptions<PlayDatasetRow<TSource> & object>): never;",
16174
+ " readonly run: { readonly id: string };",
16173
16175
  ` runSteps<TInput extends Record<string, unknown>, TOutput>(program: RunnableStepProgram<TInput, TOutput>, input: TInput, options?: { description?: ${cloudReferenceType("ctx.runSteps.options.description")} }): Promise<TOutput>;`,
16174
16176
  " tools: { execute<K extends string>(request: ToolExecutionRequest<K>): Promise<ToolExecutionOutput<K>> };",
16175
16177
  ` customerDb: { query<TRow extends Record<string, unknown> = Record<string, unknown>>(statement: SqlQuery, options?: { maxRows?: ${cloudReferenceType("ctx.customerDb.query.options.maxRows")}; timeoutMs?: ${cloudReferenceType("ctx.customerDb.query.options.timeoutMs")} }): Promise<TRow[]> };`,
@@ -21669,6 +21671,11 @@ function printPlayTriggers(triggers) {
21669
21671
  const whereNote = listener.where ? " (with where filter)" : "";
21670
21672
  console.log(` sqlListeners \u2192 ${target} on ${operations}${whereNote}`);
21671
21673
  }
21674
+ if (triggers.sqlListenerEvent) {
21675
+ console.log(
21676
+ ` sqlListener input \u2192 one top-level event per matched row: ${triggers.sqlListenerEvent.fields.join(", ")}`
21677
+ );
21678
+ }
21672
21679
  if (triggers.cron) {
21673
21680
  const timezone = triggers.cron.timezone ? ` (${triggers.cron.timezone})` : "";
21674
21681
  console.log(` cron \u2192 ${triggers.cron.schedule}${timezone}`);
@@ -23630,6 +23637,8 @@ Notes:
23630
23637
  without starting a run or spending Deepline credits.
23631
23638
  Local .play.ts checks bundle the play, validate its artifact, and stop before
23632
23639
  any cloud run is created.
23640
+ SQL-listener checks echo one top-level changed-row input event under
23641
+ recognized.triggers.sqlListenerEvent; listeners do not receive an events batch.
23633
23642
 
23634
23643
  Examples:
23635
23644
  deepline plays check prebuilt/name-and-domain-to-email-waterfall-batch
@@ -24663,7 +24672,7 @@ function getterFromLegacyExtractJs(extractJs, fallbackAlias) {
24663
24672
  return null;
24664
24673
  }
24665
24674
 
24666
- // src/cli/enrich-compat-adapter.ts
24675
+ // ../shared_libs/plays/enrich-compat-adapter.ts
24667
24676
  var ENRICH_COMPAT_DEFAULT_PLAY_NAME = "deepline-enrich-v1-compat";
24668
24677
  var ENRICH_COMPAT_DEFAULT_MAP_NAME = "deepline_enrich_rows";
24669
24678
  function buildEnrichCompatibilityPlan(options = {}) {
@@ -24673,7 +24682,7 @@ function buildEnrichCompatibilityPlan(options = {}) {
24673
24682
  };
24674
24683
  }
24675
24684
 
24676
- // src/cli/user-code-safety.ts
24685
+ // ../shared_libs/plays/user-code-safety.ts
24677
24686
  var FORBIDDEN = [
24678
24687
  // Non-deterministic — breaks replay.
24679
24688
  { pattern: /\bMath\s*\.\s*random\b/, reason: "Math.random()" },
@@ -24710,7 +24719,7 @@ function assertUserCodeIsSafe(code, label) {
24710
24719
  }
24711
24720
  }
24712
24721
 
24713
- // src/cli/enrich-play-compiler.ts
24722
+ // ../shared_libs/plays/enrich-play-compiler.ts
24714
24723
  function isWaterfall(command) {
24715
24724
  return "with_waterfall" in command;
24716
24725
  }
@@ -25141,7 +25150,9 @@ function compileEnrichConfigToPlaySource(config, options = {}) {
25141
25150
  const generatedAliases = collectGeneratedAliases(config.commands);
25142
25151
  const runOptionsSource = options.failFast ? `{ key: (row, index) => __dlEnrichRowKey(row, index + rowStart), onRowError: 'fail' as const }` : `{ key: (row, index) => __dlEnrichRowKey(row, index + rowStart) }`;
25143
25152
  const playOptionsSource = [
25144
- `description: ${stringLiteral("Read a CSV file, run the configured Deepline enrich commands, and return enriched rows.")}`,
25153
+ `description: ${stringLiteral(
25154
+ "Read a CSV file, run the configured Deepline enrich commands, and return enriched rows."
25155
+ )}`,
25145
25156
  ...options.maxCreditsPerRun === void 0 ? [] : [`billing: { maxCreditsPerRun: ${String(options.maxCreditsPerRun)} }`]
25146
25157
  ].join(", ");
25147
25158
  const body = [
@@ -30858,6 +30869,20 @@ function renderDeployReplacementWarning(input2) {
30858
30869
  );
30859
30870
  return lines;
30860
30871
  }
30872
+ function renderDeployFilterRemovalWarning(payload) {
30873
+ const summary = asRecord2(payload.change_summary);
30874
+ const definition = summary ? asRecord2(summary.definition) : void 0;
30875
+ const changed = definition && Array.isArray(definition.changed) ? definition.changed : [];
30876
+ const removed = changed.some((raw) => {
30877
+ const item = asRecord2(raw);
30878
+ return asString(item?.path) === "payload.job_titles" && item?.before != null && item.after == null;
30879
+ });
30880
+ return removed ? [
30881
+ "WARNING: this full deploy removes the job_titles filter.",
30882
+ "Future findings will be unfiltered. Existing Customer DB rows are preserved.",
30883
+ 'Use `deepline monitors update <key> {"payload":{"job_titles":"..."}}` to change only the filter.'
30884
+ ] : [];
30885
+ }
30861
30886
  function renderMonitorDeployCompletion(payload) {
30862
30887
  const monitor = asRecord2(payload.monitor);
30863
30888
  const key = monitor ? asString(monitor.key) : void 0;
@@ -30891,6 +30916,8 @@ function renderMonitorDeployCompletion(payload) {
30891
30916
  monitorKey: key
30892
30917
  });
30893
30918
  if (replacementWarning.length) lines.push("", ...replacementWarning);
30919
+ const filterRemovalWarning = renderDeployFilterRemovalWarning(payload);
30920
+ if (filterRemovalWarning.length) lines.push("", ...filterRemovalWarning);
30894
30921
  const guidance = asRecord2(payload.setup_guidance);
30895
30922
  if (guidance) {
30896
30923
  const callbackUrl = asString(guidance.callback_url);
@@ -30947,6 +30974,8 @@ function renderMonitorDeployPlan(payload) {
30947
30974
  completed: false
30948
30975
  });
30949
30976
  if (replacementWarning.length) lines.push("", ...replacementWarning);
30977
+ const filterRemovalWarning = renderDeployFilterRemovalWarning(payload);
30978
+ if (filterRemovalWarning.length) lines.push("", ...filterRemovalWarning);
30950
30979
  const estimate = asRecord2(payload.deploy_cost_estimate);
30951
30980
  const credits = estimate ? asFiniteNumber(estimate.credits) : void 0;
30952
30981
  if (credits !== void 0) {
@@ -31153,11 +31182,31 @@ function renderDeployedListText(payload, requestedStatus) {
31153
31182
  if (hasLastReceivedEvent) {
31154
31183
  lines.push(` last received: ${lastReceivedEvent ?? "never"}`);
31155
31184
  }
31185
+ if (Array.isArray(entry.bound_plays)) {
31186
+ for (const rawPlay of entry.bound_plays) {
31187
+ const play = asRecord2(rawPlay);
31188
+ const playName = asString(play?.name) ?? "unnamed Play";
31189
+ const health = asRecord2(play?.consumer_health);
31190
+ const healthRequested = Boolean(play && "consumer_health" in play);
31191
+ const state = healthRequested ? asString(health?.last_run_status) ?? "no delivery yet" : "health not requested";
31192
+ const lastError = asString(health?.last_error);
31193
+ lines.push(
31194
+ ` consumer: ${playName} \u2014 ${state}${lastError ? ` (${lastError})` : ""}`
31195
+ );
31196
+ }
31197
+ }
31156
31198
  }
31157
31199
  const applied = asString(payload.status_filter_applied) ?? requestedStatus ?? "active";
31158
31200
  lines.push(
31159
31201
  `Status filter: ${applied}${requestedStatus === void 0 && applied === "active" ? " (default)" : ""} \u2014 use --status all to include disabled monitors.`
31160
31202
  );
31203
+ const summary = Array.isArray(payload.status_summary) ? payload.status_summary.flatMap((raw) => {
31204
+ const item = asRecord2(raw);
31205
+ const status = item ? asString(item.status) : void 0;
31206
+ const count = item ? asFiniteNumber(item.count) : void 0;
31207
+ return status && count !== void 0 ? [`${status}: ${count}`] : [];
31208
+ }).join(" ") : "";
31209
+ if (summary) lines.push(`All lifecycle states: ${summary}`);
31161
31210
  if (payload.is_truncated === true) {
31162
31211
  const nextCursor = asString(payload.next_cursor);
31163
31212
  lines.push(
@@ -31176,7 +31225,8 @@ async function handleMonitorsList(options) {
31176
31225
  ...options.status ? { status: options.status } : {},
31177
31226
  ...options.limit ? { limit: options.limit } : {},
31178
31227
  ...options.cursor ? { cursor: options.cursor } : {},
31179
- ...options.compact ? { compact: true } : {}
31228
+ ...options.compact ? { compact: true } : {},
31229
+ ...options.includeConsumers ? { includeConsumers: true } : {}
31180
31230
  });
31181
31231
  printCommandEnvelope(payload, {
31182
31232
  json: options.json,
@@ -31239,6 +31289,10 @@ function renderMonitorGet(payload) {
31239
31289
  const webhook = asRecord2(payload.webhook);
31240
31290
  const guidance = asRecord2(payload.setup_guidance);
31241
31291
  const plays = dependents && Array.isArray(dependents.plays) ? dependents.plays : [];
31292
+ const boundPlays = Array.isArray(payload.bound_plays) ? payload.bound_plays.flatMap((value) => {
31293
+ const play = asRecord2(value);
31294
+ return play ? [play] : [];
31295
+ }) : [];
31242
31296
  const lines = [
31243
31297
  `Monitor: ${key}`,
31244
31298
  `Tool: ${tool}`,
@@ -31280,6 +31334,31 @@ function renderMonitorGet(payload) {
31280
31334
  ` deepline monitors test ${key} '${JSON.stringify(samplePayload2)}' --json`
31281
31335
  );
31282
31336
  }
31337
+ lines.push("", `Current SQL listener consumers (${boundPlays.length}):`);
31338
+ if (boundPlays.length === 0) {
31339
+ lines.push(" none");
31340
+ } else {
31341
+ for (const play of boundPlays) {
31342
+ const name = asString(play.name) ?? "unnamed Play";
31343
+ const listener = asString(play.listener_key);
31344
+ const health = asRecord2(play.consumer_health);
31345
+ const status2 = asString(health?.last_run_status) ?? "no delivery yet";
31346
+ const lastDelivery = asFiniteNumber(health?.last_delivery_at);
31347
+ const error = asString(health?.last_error);
31348
+ lines.push(
31349
+ ` ${name}${listener ? ` (listener: ${listener})` : ""}: ${status2}`
31350
+ );
31351
+ if (lastDelivery !== void 0) {
31352
+ lines.push(
31353
+ ` last delivery: ${new Date(lastDelivery).toISOString()}`
31354
+ );
31355
+ }
31356
+ if (error) lines.push(` error: ${error}`);
31357
+ }
31358
+ }
31359
+ if (payload.consumer_health_truncated === true) {
31360
+ lines.push(" Results are truncated; use --json for the returned detail.");
31361
+ }
31283
31362
  lines.push("", `Dependent published plays (${plays.length}):`);
31284
31363
  if (plays.length === 0) {
31285
31364
  lines.push(" none");
@@ -31304,7 +31383,11 @@ function renderMonitorGet(payload) {
31304
31383
  async function handleMonitorsGet(key, options) {
31305
31384
  const client2 = new DeeplineClient();
31306
31385
  const payload = await client2.monitors.get(key);
31307
- const dependents = await client2.monitors.dependents(key);
31386
+ const boundPlays = Array.isArray(payload.bound_plays) ? payload.bound_plays : null;
31387
+ const dependents = boundPlays ? {
31388
+ plays: boundPlays,
31389
+ truncated: payload.consumer_health_truncated === true
31390
+ } : await client2.monitors.dependents(key);
31308
31391
  const detail = { ...payload, dependents };
31309
31392
  printCommandEnvelope(detail, {
31310
31393
  json: options.json,
@@ -31317,7 +31400,8 @@ async function handleMonitorsTest(key, payload, options) {
31317
31400
  key,
31318
31401
  explicitPayload,
31319
31402
  {
31320
- validationOnly: options.dispatch !== true
31403
+ validationOnly: options.dispatch !== true,
31404
+ dispatch: options.dispatch === true
31321
31405
  }
31322
31406
  );
31323
31407
  const dispatch = options.dispatch === true;
@@ -31503,12 +31587,17 @@ Notes:
31503
31587
  not the page size), returned, is_truncated, and next_cursor. When is_truncated
31504
31588
  is true, page with --cursor <next_cursor> until it is false \u2014 a "no matching
31505
31589
  monitor" reuse conclusion off a truncated page deploys a duplicate paid feed.
31590
+ The default list stays cheap: it includes the current downstream Play binding
31591
+ metadata but does not resolve per-monitor delivery/run health. Pass
31592
+ --include-consumers with --limit 20 or fewer to include the current
31593
+ SQL-listener delivery/run health for each returned monitor.
31506
31594
 
31507
31595
  Examples:
31508
31596
  deepline monitors list
31509
31597
  deepline monitors list --status all --json
31510
31598
  deepline monitors list --status all --cursor <next_cursor> --json
31511
31599
  deepline monitors list --compact --json
31600
+ deepline monitors list --include-consumers --limit 20 --json
31512
31601
  `
31513
31602
  ).option(
31514
31603
  "--status <status>",
@@ -31516,7 +31605,10 @@ Examples:
31516
31605
  ).option("--limit <n>", "Limit the number of deployed monitors returned").option(
31517
31606
  "--cursor <cursor>",
31518
31607
  "Page past a truncated result using the next_cursor from a prior list response"
31519
- ).option("--compact", COMPACT_OPTION_DESCRIPTION)
31608
+ ).option("--compact", COMPACT_OPTION_DESCRIPTION).option(
31609
+ "--include-consumers",
31610
+ "Include current SQL-listener consumer health (requires --limit 20 or fewer)"
31611
+ )
31520
31612
  ).action(monitorsAction(handleMonitorsList));
31521
31613
  withJsonOption(
31522
31614
  monitors.command("get <key>").description("Show a single deployed monitor by its public key.").addHelpText(
@@ -31566,10 +31658,11 @@ Notes:
31566
31658
  and optional controls (Deepline lifecycle metadata). Pass it positionally,
31567
31659
  via --file <path>, or
31568
31660
  from stdin with --file -. Does not deploy or spend credits.
31569
- For Deepline Native Company Radar monitors, check validates persona-filter enums and the
31570
- job_titles Boolean-expression grammar locally (parentheses are unsupported;
31571
- NOT > AND > OR).
31572
- Inspect the exact schema with
31661
+ Check validates the local definition only. It does not contact an existing
31662
+ upstream resource, prove that future events will arrive, or verify downstream
31663
+ Play delivery. The JSON response reports this validation scope explicitly.
31664
+ Provider-specific grammar, precedence, enum applicability, examples, and
31665
+ update semantics are returned by the live monitor contract. Inspect it with
31573
31666
  \`deepline monitors available deepline_native.company_radar --json\`.
31574
31667
 
31575
31668
  Examples:
@@ -31678,7 +31771,10 @@ Examples:
31678
31771
  ).option("--limit <n>", "Limit the number of deployed monitors returned").option(
31679
31772
  "--cursor <cursor>",
31680
31773
  "Page past a truncated result using the next_cursor from a prior list response"
31681
- ).option("--compact", COMPACT_OPTION_DESCRIPTION)
31774
+ ).option("--compact", COMPACT_OPTION_DESCRIPTION).option(
31775
+ "--include-consumers",
31776
+ "Include current SQL-listener consumer health (requires --limit 20 or fewer)"
31777
+ )
31682
31778
  ).action(monitorsAction(handleMonitorsList));
31683
31779
  withJsonOption(
31684
31780
  deployed.command("get <key>").description("Alias of `monitors get <key>`.")
@@ -1030,7 +1030,7 @@ var SDK_RELEASE = {
1030
1030
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
1031
1031
  // exposed storage-dependent synchronous access. This deliberate minor
1032
1032
  // release keeps lazy paging semantics independent of row residency.
1033
- version: "0.2.49",
1033
+ version: "0.2.51",
1034
1034
  contracts: {
1035
1035
  api: {
1036
1036
  name: "sdk-http-api",
@@ -6020,6 +6020,7 @@ var DeeplineClient = class {
6020
6020
  params.set("limit", String(options.limit));
6021
6021
  if (options?.cursor) params.set("cursor", options.cursor);
6022
6022
  if (options?.compact) params.set("compact", "true");
6023
+ if (options?.includeConsumers) params.set("include_consumers", "true");
6023
6024
  const query = params.toString();
6024
6025
  const suffix = query ? `?${query}` : "";
6025
6026
  return this.http.request(
@@ -6041,7 +6042,7 @@ var DeeplineClient = class {
6041
6042
  method: "POST",
6042
6043
  body: {
6043
6044
  payload,
6044
- ...options?.validationOnly ? { mode: "validation_only" } : {}
6045
+ ...options?.validationOnly ? { mode: "validation_only" } : options?.dispatch ? { mode: "dispatch" } : {}
6045
6046
  }
6046
6047
  }
6047
6048
  );
@@ -12313,7 +12314,9 @@ import {
12313
12314
  extname,
12314
12315
  isAbsolute as isAbsolute3,
12315
12316
  join as join7,
12316
- resolve as resolve8
12317
+ relative as relative2,
12318
+ resolve as resolve8,
12319
+ sep
12317
12320
  } from "path";
12318
12321
  import { builtinModules } from "module";
12319
12322
  import { Parser } from "acorn";
@@ -16207,6 +16210,7 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
16207
16210
  " csv<T = Record<string, unknown>>(path: string | CsvInput<T & object>, options?: CsvOptions): Promise<PlayDataset<T>>;",
16208
16211
  " dataset<TSource extends PlayDatasetInput<object>>(key: string, items: TSource): DatasetBuilder<PlayDatasetRow<TSource> & object, PlayDatasetRow<TSource> & object>;",
16209
16212
  " map<TSource extends PlayDatasetInput<object>>(key: string, items: TSource, options?: DatasetDefinitionOptions<PlayDatasetRow<TSource> & object>): never;",
16213
+ " readonly run: { readonly id: string };",
16210
16214
  ` runSteps<TInput extends Record<string, unknown>, TOutput>(program: RunnableStepProgram<TInput, TOutput>, input: TInput, options?: { description?: ${cloudReferenceType("ctx.runSteps.options.description")} }): Promise<TOutput>;`,
16211
16215
  " tools: { execute<K extends string>(request: ToolExecutionRequest<K>): Promise<ToolExecutionOutput<K>> };",
16212
16216
  ` customerDb: { query<TRow extends Record<string, unknown> = Record<string, unknown>>(statement: SqlQuery, options?: { maxRows?: ${cloudReferenceType("ctx.customerDb.query.options.maxRows")}; timeoutMs?: ${cloudReferenceType("ctx.customerDb.query.options.timeoutMs")} }): Promise<TRow[]> };`,
@@ -21713,6 +21717,11 @@ function printPlayTriggers(triggers) {
21713
21717
  const whereNote = listener.where ? " (with where filter)" : "";
21714
21718
  console.log(` sqlListeners \u2192 ${target} on ${operations}${whereNote}`);
21715
21719
  }
21720
+ if (triggers.sqlListenerEvent) {
21721
+ console.log(
21722
+ ` sqlListener input \u2192 one top-level event per matched row: ${triggers.sqlListenerEvent.fields.join(", ")}`
21723
+ );
21724
+ }
21716
21725
  if (triggers.cron) {
21717
21726
  const timezone = triggers.cron.timezone ? ` (${triggers.cron.timezone})` : "";
21718
21727
  console.log(` cron \u2192 ${triggers.cron.schedule}${timezone}`);
@@ -23674,6 +23683,8 @@ Notes:
23674
23683
  without starting a run or spending Deepline credits.
23675
23684
  Local .play.ts checks bundle the play, validate its artifact, and stop before
23676
23685
  any cloud run is created.
23686
+ SQL-listener checks echo one top-level changed-row input event under
23687
+ recognized.triggers.sqlListenerEvent; listeners do not receive an events batch.
23677
23688
 
23678
23689
  Examples:
23679
23690
  deepline plays check prebuilt/name-and-domain-to-email-waterfall-batch
@@ -24707,7 +24718,7 @@ function getterFromLegacyExtractJs(extractJs, fallbackAlias) {
24707
24718
  return null;
24708
24719
  }
24709
24720
 
24710
- // src/cli/enrich-compat-adapter.ts
24721
+ // ../shared_libs/plays/enrich-compat-adapter.ts
24711
24722
  var ENRICH_COMPAT_DEFAULT_PLAY_NAME = "deepline-enrich-v1-compat";
24712
24723
  var ENRICH_COMPAT_DEFAULT_MAP_NAME = "deepline_enrich_rows";
24713
24724
  function buildEnrichCompatibilityPlan(options = {}) {
@@ -24717,7 +24728,7 @@ function buildEnrichCompatibilityPlan(options = {}) {
24717
24728
  };
24718
24729
  }
24719
24730
 
24720
- // src/cli/user-code-safety.ts
24731
+ // ../shared_libs/plays/user-code-safety.ts
24721
24732
  var FORBIDDEN = [
24722
24733
  // Non-deterministic — breaks replay.
24723
24734
  { pattern: /\bMath\s*\.\s*random\b/, reason: "Math.random()" },
@@ -24754,7 +24765,7 @@ function assertUserCodeIsSafe(code, label) {
24754
24765
  }
24755
24766
  }
24756
24767
 
24757
- // src/cli/enrich-play-compiler.ts
24768
+ // ../shared_libs/plays/enrich-play-compiler.ts
24758
24769
  function isWaterfall(command) {
24759
24770
  return "with_waterfall" in command;
24760
24771
  }
@@ -25185,7 +25196,9 @@ function compileEnrichConfigToPlaySource(config, options = {}) {
25185
25196
  const generatedAliases = collectGeneratedAliases(config.commands);
25186
25197
  const runOptionsSource = options.failFast ? `{ key: (row, index) => __dlEnrichRowKey(row, index + rowStart), onRowError: 'fail' as const }` : `{ key: (row, index) => __dlEnrichRowKey(row, index + rowStart) }`;
25187
25198
  const playOptionsSource = [
25188
- `description: ${stringLiteral("Read a CSV file, run the configured Deepline enrich commands, and return enriched rows.")}`,
25199
+ `description: ${stringLiteral(
25200
+ "Read a CSV file, run the configured Deepline enrich commands, and return enriched rows."
25201
+ )}`,
25189
25202
  ...options.maxCreditsPerRun === void 0 ? [] : [`billing: { maxCreditsPerRun: ${String(options.maxCreditsPerRun)} }`]
25190
25203
  ].join(", ");
25191
25204
  const body = [
@@ -30909,6 +30922,20 @@ function renderDeployReplacementWarning(input2) {
30909
30922
  );
30910
30923
  return lines;
30911
30924
  }
30925
+ function renderDeployFilterRemovalWarning(payload) {
30926
+ const summary = asRecord2(payload.change_summary);
30927
+ const definition = summary ? asRecord2(summary.definition) : void 0;
30928
+ const changed = definition && Array.isArray(definition.changed) ? definition.changed : [];
30929
+ const removed = changed.some((raw) => {
30930
+ const item = asRecord2(raw);
30931
+ return asString(item?.path) === "payload.job_titles" && item?.before != null && item.after == null;
30932
+ });
30933
+ return removed ? [
30934
+ "WARNING: this full deploy removes the job_titles filter.",
30935
+ "Future findings will be unfiltered. Existing Customer DB rows are preserved.",
30936
+ 'Use `deepline monitors update <key> {"payload":{"job_titles":"..."}}` to change only the filter.'
30937
+ ] : [];
30938
+ }
30912
30939
  function renderMonitorDeployCompletion(payload) {
30913
30940
  const monitor = asRecord2(payload.monitor);
30914
30941
  const key = monitor ? asString(monitor.key) : void 0;
@@ -30942,6 +30969,8 @@ function renderMonitorDeployCompletion(payload) {
30942
30969
  monitorKey: key
30943
30970
  });
30944
30971
  if (replacementWarning.length) lines.push("", ...replacementWarning);
30972
+ const filterRemovalWarning = renderDeployFilterRemovalWarning(payload);
30973
+ if (filterRemovalWarning.length) lines.push("", ...filterRemovalWarning);
30945
30974
  const guidance = asRecord2(payload.setup_guidance);
30946
30975
  if (guidance) {
30947
30976
  const callbackUrl = asString(guidance.callback_url);
@@ -30998,6 +31027,8 @@ function renderMonitorDeployPlan(payload) {
30998
31027
  completed: false
30999
31028
  });
31000
31029
  if (replacementWarning.length) lines.push("", ...replacementWarning);
31030
+ const filterRemovalWarning = renderDeployFilterRemovalWarning(payload);
31031
+ if (filterRemovalWarning.length) lines.push("", ...filterRemovalWarning);
31001
31032
  const estimate = asRecord2(payload.deploy_cost_estimate);
31002
31033
  const credits = estimate ? asFiniteNumber(estimate.credits) : void 0;
31003
31034
  if (credits !== void 0) {
@@ -31204,11 +31235,31 @@ function renderDeployedListText(payload, requestedStatus) {
31204
31235
  if (hasLastReceivedEvent) {
31205
31236
  lines.push(` last received: ${lastReceivedEvent ?? "never"}`);
31206
31237
  }
31238
+ if (Array.isArray(entry.bound_plays)) {
31239
+ for (const rawPlay of entry.bound_plays) {
31240
+ const play = asRecord2(rawPlay);
31241
+ const playName = asString(play?.name) ?? "unnamed Play";
31242
+ const health = asRecord2(play?.consumer_health);
31243
+ const healthRequested = Boolean(play && "consumer_health" in play);
31244
+ const state = healthRequested ? asString(health?.last_run_status) ?? "no delivery yet" : "health not requested";
31245
+ const lastError = asString(health?.last_error);
31246
+ lines.push(
31247
+ ` consumer: ${playName} \u2014 ${state}${lastError ? ` (${lastError})` : ""}`
31248
+ );
31249
+ }
31250
+ }
31207
31251
  }
31208
31252
  const applied = asString(payload.status_filter_applied) ?? requestedStatus ?? "active";
31209
31253
  lines.push(
31210
31254
  `Status filter: ${applied}${requestedStatus === void 0 && applied === "active" ? " (default)" : ""} \u2014 use --status all to include disabled monitors.`
31211
31255
  );
31256
+ const summary = Array.isArray(payload.status_summary) ? payload.status_summary.flatMap((raw) => {
31257
+ const item = asRecord2(raw);
31258
+ const status = item ? asString(item.status) : void 0;
31259
+ const count = item ? asFiniteNumber(item.count) : void 0;
31260
+ return status && count !== void 0 ? [`${status}: ${count}`] : [];
31261
+ }).join(" ") : "";
31262
+ if (summary) lines.push(`All lifecycle states: ${summary}`);
31212
31263
  if (payload.is_truncated === true) {
31213
31264
  const nextCursor = asString(payload.next_cursor);
31214
31265
  lines.push(
@@ -31227,7 +31278,8 @@ async function handleMonitorsList(options) {
31227
31278
  ...options.status ? { status: options.status } : {},
31228
31279
  ...options.limit ? { limit: options.limit } : {},
31229
31280
  ...options.cursor ? { cursor: options.cursor } : {},
31230
- ...options.compact ? { compact: true } : {}
31281
+ ...options.compact ? { compact: true } : {},
31282
+ ...options.includeConsumers ? { includeConsumers: true } : {}
31231
31283
  });
31232
31284
  printCommandEnvelope(payload, {
31233
31285
  json: options.json,
@@ -31290,6 +31342,10 @@ function renderMonitorGet(payload) {
31290
31342
  const webhook = asRecord2(payload.webhook);
31291
31343
  const guidance = asRecord2(payload.setup_guidance);
31292
31344
  const plays = dependents && Array.isArray(dependents.plays) ? dependents.plays : [];
31345
+ const boundPlays = Array.isArray(payload.bound_plays) ? payload.bound_plays.flatMap((value) => {
31346
+ const play = asRecord2(value);
31347
+ return play ? [play] : [];
31348
+ }) : [];
31293
31349
  const lines = [
31294
31350
  `Monitor: ${key}`,
31295
31351
  `Tool: ${tool}`,
@@ -31331,6 +31387,31 @@ function renderMonitorGet(payload) {
31331
31387
  ` deepline monitors test ${key} '${JSON.stringify(samplePayload2)}' --json`
31332
31388
  );
31333
31389
  }
31390
+ lines.push("", `Current SQL listener consumers (${boundPlays.length}):`);
31391
+ if (boundPlays.length === 0) {
31392
+ lines.push(" none");
31393
+ } else {
31394
+ for (const play of boundPlays) {
31395
+ const name = asString(play.name) ?? "unnamed Play";
31396
+ const listener = asString(play.listener_key);
31397
+ const health = asRecord2(play.consumer_health);
31398
+ const status2 = asString(health?.last_run_status) ?? "no delivery yet";
31399
+ const lastDelivery = asFiniteNumber(health?.last_delivery_at);
31400
+ const error = asString(health?.last_error);
31401
+ lines.push(
31402
+ ` ${name}${listener ? ` (listener: ${listener})` : ""}: ${status2}`
31403
+ );
31404
+ if (lastDelivery !== void 0) {
31405
+ lines.push(
31406
+ ` last delivery: ${new Date(lastDelivery).toISOString()}`
31407
+ );
31408
+ }
31409
+ if (error) lines.push(` error: ${error}`);
31410
+ }
31411
+ }
31412
+ if (payload.consumer_health_truncated === true) {
31413
+ lines.push(" Results are truncated; use --json for the returned detail.");
31414
+ }
31334
31415
  lines.push("", `Dependent published plays (${plays.length}):`);
31335
31416
  if (plays.length === 0) {
31336
31417
  lines.push(" none");
@@ -31355,7 +31436,11 @@ function renderMonitorGet(payload) {
31355
31436
  async function handleMonitorsGet(key, options) {
31356
31437
  const client2 = new DeeplineClient();
31357
31438
  const payload = await client2.monitors.get(key);
31358
- const dependents = await client2.monitors.dependents(key);
31439
+ const boundPlays = Array.isArray(payload.bound_plays) ? payload.bound_plays : null;
31440
+ const dependents = boundPlays ? {
31441
+ plays: boundPlays,
31442
+ truncated: payload.consumer_health_truncated === true
31443
+ } : await client2.monitors.dependents(key);
31359
31444
  const detail = { ...payload, dependents };
31360
31445
  printCommandEnvelope(detail, {
31361
31446
  json: options.json,
@@ -31368,7 +31453,8 @@ async function handleMonitorsTest(key, payload, options) {
31368
31453
  key,
31369
31454
  explicitPayload,
31370
31455
  {
31371
- validationOnly: options.dispatch !== true
31456
+ validationOnly: options.dispatch !== true,
31457
+ dispatch: options.dispatch === true
31372
31458
  }
31373
31459
  );
31374
31460
  const dispatch = options.dispatch === true;
@@ -31554,12 +31640,17 @@ Notes:
31554
31640
  not the page size), returned, is_truncated, and next_cursor. When is_truncated
31555
31641
  is true, page with --cursor <next_cursor> until it is false \u2014 a "no matching
31556
31642
  monitor" reuse conclusion off a truncated page deploys a duplicate paid feed.
31643
+ The default list stays cheap: it includes the current downstream Play binding
31644
+ metadata but does not resolve per-monitor delivery/run health. Pass
31645
+ --include-consumers with --limit 20 or fewer to include the current
31646
+ SQL-listener delivery/run health for each returned monitor.
31557
31647
 
31558
31648
  Examples:
31559
31649
  deepline monitors list
31560
31650
  deepline monitors list --status all --json
31561
31651
  deepline monitors list --status all --cursor <next_cursor> --json
31562
31652
  deepline monitors list --compact --json
31653
+ deepline monitors list --include-consumers --limit 20 --json
31563
31654
  `
31564
31655
  ).option(
31565
31656
  "--status <status>",
@@ -31567,7 +31658,10 @@ Examples:
31567
31658
  ).option("--limit <n>", "Limit the number of deployed monitors returned").option(
31568
31659
  "--cursor <cursor>",
31569
31660
  "Page past a truncated result using the next_cursor from a prior list response"
31570
- ).option("--compact", COMPACT_OPTION_DESCRIPTION)
31661
+ ).option("--compact", COMPACT_OPTION_DESCRIPTION).option(
31662
+ "--include-consumers",
31663
+ "Include current SQL-listener consumer health (requires --limit 20 or fewer)"
31664
+ )
31571
31665
  ).action(monitorsAction(handleMonitorsList));
31572
31666
  withJsonOption(
31573
31667
  monitors.command("get <key>").description("Show a single deployed monitor by its public key.").addHelpText(
@@ -31617,10 +31711,11 @@ Notes:
31617
31711
  and optional controls (Deepline lifecycle metadata). Pass it positionally,
31618
31712
  via --file <path>, or
31619
31713
  from stdin with --file -. Does not deploy or spend credits.
31620
- For Deepline Native Company Radar monitors, check validates persona-filter enums and the
31621
- job_titles Boolean-expression grammar locally (parentheses are unsupported;
31622
- NOT > AND > OR).
31623
- Inspect the exact schema with
31714
+ Check validates the local definition only. It does not contact an existing
31715
+ upstream resource, prove that future events will arrive, or verify downstream
31716
+ Play delivery. The JSON response reports this validation scope explicitly.
31717
+ Provider-specific grammar, precedence, enum applicability, examples, and
31718
+ update semantics are returned by the live monitor contract. Inspect it with
31624
31719
  \`deepline monitors available deepline_native.company_radar --json\`.
31625
31720
 
31626
31721
  Examples:
@@ -31729,7 +31824,10 @@ Examples:
31729
31824
  ).option("--limit <n>", "Limit the number of deployed monitors returned").option(
31730
31825
  "--cursor <cursor>",
31731
31826
  "Page past a truncated result using the next_cursor from a prior list response"
31732
- ).option("--compact", COMPACT_OPTION_DESCRIPTION)
31827
+ ).option("--compact", COMPACT_OPTION_DESCRIPTION).option(
31828
+ "--include-consumers",
31829
+ "Include current SQL-listener consumer health (requires --limit 20 or fewer)"
31830
+ )
31733
31831
  ).action(monitorsAction(handleMonitorsList));
31734
31832
  withJsonOption(
31735
31833
  deployed.command("get <key>").description("Alias of `monitors get <key>`.")