deepline 0.1.107 → 0.1.108

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/cli/index.js CHANGED
@@ -244,16 +244,18 @@ var SDK_RELEASE = {
244
244
  // 0.1.106 ships play cell provenance metadata and v2 preview retry hardening.
245
245
  // 0.1.107 ships the v2 quickstart command, the deepline-plays-quickstart
246
246
  // skill on the sdk sync surface, and the people-search-to-email prebuilt.
247
- version: "0.1.107",
247
+ // 0.1.108 ships explicit dataset column/tool recompute policy and removes
248
+ // the SDK enrich generator's one-second stale policy.
249
+ version: "0.1.108",
248
250
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
249
251
  supportPolicy: {
250
- latest: "0.1.107",
252
+ latest: "0.1.108",
251
253
  minimumSupported: "0.1.53",
252
254
  deprecatedBelow: "0.1.53",
253
255
  commandMinimumSupported: [
254
256
  {
255
257
  command: "enrich",
256
- minimumSupported: "0.1.106",
258
+ minimumSupported: "0.1.108",
257
259
  reason: "Older SDK CLI enrich generated stale play source for the current dataset API."
258
260
  }
259
261
  ],
@@ -857,6 +859,8 @@ function normalizeStepProgress(value) {
857
859
  value.artifactTableNamespace
858
860
  )
859
861
  } : {},
862
+ ...finiteNumber(value.startedAt) !== null ? { startedAt: finiteNumber(value.startedAt) } : {},
863
+ ...finiteNumber(value.completedAt) !== null ? { completedAt: finiteNumber(value.completedAt) } : {},
860
864
  ...finiteNumber(value.updatedAt) !== null ? { updatedAt: finiteNumber(value.updatedAt) } : {}
861
865
  };
862
866
  }
@@ -15491,7 +15495,7 @@ function renderIdiomaticExecuteStep(command, options) {
15491
15495
  ` tool: ${tool},`,
15492
15496
  ` input: ${input2} as any,`,
15493
15497
  ` description: ${stringLiteral((command.description ?? "").trim() || `Run ${command.alias} via ${command.tool}.`)},`,
15494
- ...options.force ? [` staleAfterSeconds: 1,`] : [],
15498
+ ...options.force ? [` force: true,`] : [],
15495
15499
  ` });`,
15496
15500
  ` return ${extraction};`,
15497
15501
  `}`
@@ -15523,17 +15527,13 @@ function renderPlayStep(command, options) {
15523
15527
  ${indent(renderJavascriptBody(command.run_if_js), 6)}
15524
15528
  }` : "null";
15525
15529
  const runIfLines = command.run_if_js ? [` const __dlRunIf = ${runIfJs};`, ` if (!__dlRunIf(row)) return null;`] : [];
15526
- const playOptions = [
15527
- ` description: ${stringLiteral(command.description ?? command.alias)}`,
15528
- ...options.force ? [` staleAfterSeconds: 1`] : []
15529
- ].join(",\n");
15530
15530
  return [
15531
15531
  `async (row, stepCtx) => {`,
15532
15532
  ...options.precheck ? [` if (${options.precheck}) return null;`] : [],
15533
15533
  ...runIfLines,
15534
15534
  ` const payload = __dlTemplate(${payload}, row) as Record<string, unknown>;`,
15535
15535
  ` const result = await stepCtx.runPlay(${callId}, ${playRef}, payload, {`,
15536
- playOptions,
15536
+ ` description: ${stringLiteral(command.description ?? command.alias)}`,
15537
15537
  ` });`,
15538
15538
  ` return __dlPlayResultValue(${alias}, result);`,
15539
15539
  `}`
@@ -15550,12 +15550,17 @@ function renderJavascriptBody(source) {
15550
15550
  }
15551
15551
  return source;
15552
15552
  }
15553
- function renderColumnStep(alias, resolverSource, force) {
15553
+ function renderColumnStep(alias, resolverSource, options = {}) {
15554
15554
  const resolver = indent(resolverSource, 8);
15555
+ const optionFields = [
15556
+ ...options.recompute === true ? ["recompute: true"] : [],
15557
+ ...options.recomputeOnError === true ? ["recomputeOnError: true"] : []
15558
+ ];
15559
+ const optionSource = optionFields.length > 0 ? `{ ${optionFields.join(", ")} }` : null;
15555
15560
  return [
15556
15561
  ` .withColumn(${stringLiteral(alias)},`,
15557
- force ? `${resolver},` : resolver,
15558
- ...force ? [` { staleAfterSeconds: 1 }`] : [],
15562
+ `${resolver}${optionSource ? "," : ""}`,
15563
+ ...optionSource ? [` ${optionSource},`] : [],
15559
15564
  ` )`
15560
15565
  ].join("\n");
15561
15566
  }
@@ -15604,6 +15609,29 @@ function collectMetadataColumns(commands, waterfallGroupId) {
15604
15609
  }
15605
15610
  return columns;
15606
15611
  }
15612
+ function collectGeneratedAliases(commands) {
15613
+ const aliases = [];
15614
+ const addAlias = (alias) => {
15615
+ if (alias && !aliases.includes(alias)) {
15616
+ aliases.push(alias);
15617
+ }
15618
+ };
15619
+ for (const command of commands) {
15620
+ if (isWaterfall(command)) {
15621
+ const before = aliases.length;
15622
+ collectGeneratedAliases(command.commands).forEach(addAlias);
15623
+ if (aliases.length > before) {
15624
+ addAlias(command.with_waterfall);
15625
+ addAlias(`${command.with_waterfall}_source`);
15626
+ }
15627
+ continue;
15628
+ }
15629
+ if (!command.disabled) {
15630
+ addAlias(command.alias);
15631
+ }
15632
+ }
15633
+ return aliases;
15634
+ }
15607
15635
  function renderMetadataColumnStep(config) {
15608
15636
  const columns = collectMetadataColumns(config.commands);
15609
15637
  if (Object.keys(columns).length === 0) {
@@ -15611,8 +15639,8 @@ function renderMetadataColumnStep(config) {
15611
15639
  }
15612
15640
  return [
15613
15641
  ` .withColumn('_metadata',`,
15614
- ` (row) => __dlMergeMetadata(row._metadata, ${stableJson({ columns })}),`,
15615
- ` { staleAfterSeconds: 1 }`,
15642
+ ` (row) => __dlMergeMetadata(__dlMetadataFromRow(row), ${stableJson({ columns })}),`,
15643
+ ` { recompute: true },`,
15616
15644
  ` )`
15617
15645
  ].join("\n");
15618
15646
  }
@@ -15639,7 +15667,7 @@ function renderWaterfallColumns(command, forceAliases, inlineRunJavascript, idio
15639
15667
  inlineRunJavascript,
15640
15668
  idiomaticGetters
15641
15669
  }),
15642
- force
15670
+ { recompute: force, recomputeOnError: true }
15643
15671
  );
15644
15672
  }).filter((line) => line !== null);
15645
15673
  const aliases = activeChildren.map((nested) => nested.alias);
@@ -15655,15 +15683,13 @@ function renderWaterfallColumns(command, forceAliases, inlineRunJavascript, idio
15655
15683
  }
15656
15684
  return [
15657
15685
  ...columnSteps,
15658
- renderColumnStep(
15659
- command.with_waterfall,
15660
- `(row) => ${returnExpr}`,
15661
- forceParent
15662
- ),
15686
+ renderColumnStep(command.with_waterfall, `(row) => ${returnExpr}`, {
15687
+ recompute: forceParent
15688
+ }),
15663
15689
  renderColumnStep(
15664
15690
  `${command.with_waterfall}_source`,
15665
15691
  typeof command.min_results === "number" ? `(row) => __dlContributingAliases(row, ${stableJson(aliases)})` : `(row) => __dlFirstMeaningfulAlias(row, ${stableJson(aliases)})`,
15666
- forceParent
15692
+ { recompute: forceParent }
15667
15693
  )
15668
15694
  ];
15669
15695
  }
@@ -15700,18 +15726,20 @@ function compileEnrichConfigToPlaySource(config, options = {}) {
15700
15726
  inlineRunJavascript,
15701
15727
  idiomaticGetters
15702
15728
  }),
15703
- force
15729
+ { recompute: force, recomputeOnError: true }
15704
15730
  )
15705
15731
  );
15706
15732
  });
15707
15733
  const columnStepSource = columnSteps.length > 0 ? columnSteps.join("\n") : ` .withColumn('noop', () => null)`;
15708
15734
  const metadataColumnSource = renderMetadataColumnStep(config);
15735
+ const generatedAliases = collectGeneratedAliases(config.commands);
15709
15736
  const body = [
15710
15737
  `export default definePlay(${stringLiteral(playName)}, async (ctx, input: EnrichInput) => {`,
15711
15738
  ` const sourceRows = await ctx.csv<Record<string, unknown>>(input.file);`,
15712
15739
  ` const rowStart = __dlNonNegativeInteger(input.rowStart, 0);`,
15713
15740
  ` const rowEndExclusive = Number.isFinite(input.rowEnd) ? Math.max(rowStart, __dlWholeNumber(input.rowEnd, rowStart) + 1) : undefined;`,
15714
- ` const rows = sourceRows.slice(rowStart, rowEndExclusive, { key: 'deepline_enrich_selected_rows', sourceLabel: 'Selected enrich rows' });`,
15741
+ ` const selectedRows = sourceRows.slice(rowStart, rowEndExclusive, { key: 'deepline_enrich_selected_rows', sourceLabel: 'Selected enrich rows' });`,
15742
+ ` const rows = selectedRows.map((row) => __dlStripErroredGeneratedColumns(row, ${stableJson(generatedAliases)}), { key: 'deepline_enrich_repair_rows', sourceLabel: 'Selected enrich rows' });`,
15715
15743
  ` const enriched = await ctx`,
15716
15744
  ` .dataset(${stringLiteral(mapName)}, rows)`,
15717
15745
  columnStepSource,
@@ -15797,6 +15825,18 @@ function helperSource() {
15797
15825
  ` return null;`,
15798
15826
  `}`,
15799
15827
  ``,
15828
+ `function __dlMetadataFromRow(row: Record<string, unknown>): unknown {`,
15829
+ ` const direct = __dlParseMetadata(row._metadata);`,
15830
+ ` if (direct) return direct;`,
15831
+ ` const relocated = __dlParseMetadata(row.metadata);`,
15832
+ ` if (relocated) return relocated;`,
15833
+ ` const dotted = __dlParseMetadata(row['metadata.columns']);`,
15834
+ ` if (dotted) return { columns: dotted };`,
15835
+ ` const underscored = __dlParseMetadata(row.metadata__columns);`,
15836
+ ` if (underscored) return { columns: underscored };`,
15837
+ ` return row._metadata;`,
15838
+ `}`,
15839
+ ``,
15800
15840
  `function __dlMergeMetadata(existing: unknown, patch: Record<string, unknown>): Record<string, unknown> {`,
15801
15841
  ` const base = __dlParseMetadata(existing) ?? {};`,
15802
15842
  ` const baseColumns = __dlRecord(base.columns) ? base.columns : {};`,
@@ -15871,6 +15911,40 @@ function helperSource() {
15871
15911
  ` return status === 'error' || status === 'failed' || (typeof record.error === 'string' && record.error.trim() !== '') || (typeof resultError === 'string' && resultError.trim() !== '');`,
15872
15912
  `}`,
15873
15913
  ``,
15914
+ `function __dlGeneratedCellError(value: unknown): boolean {`,
15915
+ ` if (typeof value === 'string') {`,
15916
+ ` const lower = value.trim().toLowerCase();`,
15917
+ ` if (!lower) return false;`,
15918
+ ` if (lower.startsWith('error:') || lower.includes(': error:') || lower.includes('"error"')) return true;`,
15919
+ ` try {`,
15920
+ ` return __dlGeneratedCellError(JSON.parse(value));`,
15921
+ ` } catch {`,
15922
+ ` return false;`,
15923
+ ` }`,
15924
+ ` }`,
15925
+ ` if (!__dlRecord(value)) return false;`,
15926
+ ` const status = typeof value.status === 'string' ? value.status.toLowerCase() : '';`,
15927
+ ` if (status === 'error' || status === 'failed') return true;`,
15928
+ ` if (typeof value.error === 'string' && value.error.trim() !== '') return true;`,
15929
+ ` const raw = __dlGetByPath(value, 'toolResponse.raw') ?? __dlGetByPath(value, 'toolOutput.raw');`,
15930
+ ` if (raw !== undefined && raw !== value && __dlGeneratedCellError(raw)) return true;`,
15931
+ ` const result = value.result;`,
15932
+ ` return result !== undefined && result !== value && __dlGeneratedCellError(result);`,
15933
+ `}`,
15934
+ ``,
15935
+ `function __dlStripErroredGeneratedColumns(row: Record<string, unknown>, aliases: string[]): Record<string, unknown> {`,
15936
+ ` let next: Record<string, unknown> | null = null;`,
15937
+ ` for (const alias of aliases) {`,
15938
+ ` for (const key of __dlAliasCandidates(alias)) {`,
15939
+ ` if (key in row && __dlGeneratedCellError(row[key])) {`,
15940
+ ` if (next === null) next = { ...row };`,
15941
+ ` delete next[key];`,
15942
+ ` }`,
15943
+ ` }`,
15944
+ ` }`,
15945
+ ` return next ?? row;`,
15946
+ `}`,
15947
+ ``,
15874
15948
  `function __dlRawToolOutput(result: unknown): unknown {`,
15875
15949
  ` if (!result || typeof result !== 'object') return result;`,
15876
15950
  ` const record = result as Record<string, unknown>;`,
@@ -16281,7 +16355,7 @@ function helperSource() {
16281
16355
  ` tool: input.tool,`,
16282
16356
  ` input: payload,`,
16283
16357
  ` ...(input.description ? { description: input.description } : {}),`,
16284
- ` ...(input.force ? { staleAfterSeconds: 1 } : {}),`,
16358
+ ` ...(input.force ? { force: true } : {}),`,
16285
16359
  ` });`,
16286
16360
  ` return __dlExtract(input.alias, result, input.row, input.extract, Boolean(input.legacyEnvelope));`,
16287
16361
  `}`
@@ -221,16 +221,18 @@ var SDK_RELEASE = {
221
221
  // 0.1.106 ships play cell provenance metadata and v2 preview retry hardening.
222
222
  // 0.1.107 ships the v2 quickstart command, the deepline-plays-quickstart
223
223
  // skill on the sdk sync surface, and the people-search-to-email prebuilt.
224
- version: "0.1.107",
224
+ // 0.1.108 ships explicit dataset column/tool recompute policy and removes
225
+ // the SDK enrich generator's one-second stale policy.
226
+ version: "0.1.108",
225
227
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
226
228
  supportPolicy: {
227
- latest: "0.1.107",
229
+ latest: "0.1.108",
228
230
  minimumSupported: "0.1.53",
229
231
  deprecatedBelow: "0.1.53",
230
232
  commandMinimumSupported: [
231
233
  {
232
234
  command: "enrich",
233
- minimumSupported: "0.1.106",
235
+ minimumSupported: "0.1.108",
234
236
  reason: "Older SDK CLI enrich generated stale play source for the current dataset API."
235
237
  }
236
238
  ],
@@ -834,6 +836,8 @@ function normalizeStepProgress(value) {
834
836
  value.artifactTableNamespace
835
837
  )
836
838
  } : {},
839
+ ...finiteNumber(value.startedAt) !== null ? { startedAt: finiteNumber(value.startedAt) } : {},
840
+ ...finiteNumber(value.completedAt) !== null ? { completedAt: finiteNumber(value.completedAt) } : {},
837
841
  ...finiteNumber(value.updatedAt) !== null ? { updatedAt: finiteNumber(value.updatedAt) } : {}
838
842
  };
839
843
  }
@@ -15508,7 +15512,7 @@ function renderIdiomaticExecuteStep(command, options) {
15508
15512
  ` tool: ${tool},`,
15509
15513
  ` input: ${input2} as any,`,
15510
15514
  ` description: ${stringLiteral((command.description ?? "").trim() || `Run ${command.alias} via ${command.tool}.`)},`,
15511
- ...options.force ? [` staleAfterSeconds: 1,`] : [],
15515
+ ...options.force ? [` force: true,`] : [],
15512
15516
  ` });`,
15513
15517
  ` return ${extraction};`,
15514
15518
  `}`
@@ -15540,17 +15544,13 @@ function renderPlayStep(command, options) {
15540
15544
  ${indent(renderJavascriptBody(command.run_if_js), 6)}
15541
15545
  }` : "null";
15542
15546
  const runIfLines = command.run_if_js ? [` const __dlRunIf = ${runIfJs};`, ` if (!__dlRunIf(row)) return null;`] : [];
15543
- const playOptions = [
15544
- ` description: ${stringLiteral(command.description ?? command.alias)}`,
15545
- ...options.force ? [` staleAfterSeconds: 1`] : []
15546
- ].join(",\n");
15547
15547
  return [
15548
15548
  `async (row, stepCtx) => {`,
15549
15549
  ...options.precheck ? [` if (${options.precheck}) return null;`] : [],
15550
15550
  ...runIfLines,
15551
15551
  ` const payload = __dlTemplate(${payload}, row) as Record<string, unknown>;`,
15552
15552
  ` const result = await stepCtx.runPlay(${callId}, ${playRef}, payload, {`,
15553
- playOptions,
15553
+ ` description: ${stringLiteral(command.description ?? command.alias)}`,
15554
15554
  ` });`,
15555
15555
  ` return __dlPlayResultValue(${alias}, result);`,
15556
15556
  `}`
@@ -15567,12 +15567,17 @@ function renderJavascriptBody(source) {
15567
15567
  }
15568
15568
  return source;
15569
15569
  }
15570
- function renderColumnStep(alias, resolverSource, force) {
15570
+ function renderColumnStep(alias, resolverSource, options = {}) {
15571
15571
  const resolver = indent(resolverSource, 8);
15572
+ const optionFields = [
15573
+ ...options.recompute === true ? ["recompute: true"] : [],
15574
+ ...options.recomputeOnError === true ? ["recomputeOnError: true"] : []
15575
+ ];
15576
+ const optionSource = optionFields.length > 0 ? `{ ${optionFields.join(", ")} }` : null;
15572
15577
  return [
15573
15578
  ` .withColumn(${stringLiteral(alias)},`,
15574
- force ? `${resolver},` : resolver,
15575
- ...force ? [` { staleAfterSeconds: 1 }`] : [],
15579
+ `${resolver}${optionSource ? "," : ""}`,
15580
+ ...optionSource ? [` ${optionSource},`] : [],
15576
15581
  ` )`
15577
15582
  ].join("\n");
15578
15583
  }
@@ -15621,6 +15626,29 @@ function collectMetadataColumns(commands, waterfallGroupId) {
15621
15626
  }
15622
15627
  return columns;
15623
15628
  }
15629
+ function collectGeneratedAliases(commands) {
15630
+ const aliases = [];
15631
+ const addAlias = (alias) => {
15632
+ if (alias && !aliases.includes(alias)) {
15633
+ aliases.push(alias);
15634
+ }
15635
+ };
15636
+ for (const command of commands) {
15637
+ if (isWaterfall(command)) {
15638
+ const before = aliases.length;
15639
+ collectGeneratedAliases(command.commands).forEach(addAlias);
15640
+ if (aliases.length > before) {
15641
+ addAlias(command.with_waterfall);
15642
+ addAlias(`${command.with_waterfall}_source`);
15643
+ }
15644
+ continue;
15645
+ }
15646
+ if (!command.disabled) {
15647
+ addAlias(command.alias);
15648
+ }
15649
+ }
15650
+ return aliases;
15651
+ }
15624
15652
  function renderMetadataColumnStep(config) {
15625
15653
  const columns = collectMetadataColumns(config.commands);
15626
15654
  if (Object.keys(columns).length === 0) {
@@ -15628,8 +15656,8 @@ function renderMetadataColumnStep(config) {
15628
15656
  }
15629
15657
  return [
15630
15658
  ` .withColumn('_metadata',`,
15631
- ` (row) => __dlMergeMetadata(row._metadata, ${stableJson({ columns })}),`,
15632
- ` { staleAfterSeconds: 1 }`,
15659
+ ` (row) => __dlMergeMetadata(__dlMetadataFromRow(row), ${stableJson({ columns })}),`,
15660
+ ` { recompute: true },`,
15633
15661
  ` )`
15634
15662
  ].join("\n");
15635
15663
  }
@@ -15656,7 +15684,7 @@ function renderWaterfallColumns(command, forceAliases, inlineRunJavascript, idio
15656
15684
  inlineRunJavascript,
15657
15685
  idiomaticGetters
15658
15686
  }),
15659
- force
15687
+ { recompute: force, recomputeOnError: true }
15660
15688
  );
15661
15689
  }).filter((line) => line !== null);
15662
15690
  const aliases = activeChildren.map((nested) => nested.alias);
@@ -15672,15 +15700,13 @@ function renderWaterfallColumns(command, forceAliases, inlineRunJavascript, idio
15672
15700
  }
15673
15701
  return [
15674
15702
  ...columnSteps,
15675
- renderColumnStep(
15676
- command.with_waterfall,
15677
- `(row) => ${returnExpr}`,
15678
- forceParent
15679
- ),
15703
+ renderColumnStep(command.with_waterfall, `(row) => ${returnExpr}`, {
15704
+ recompute: forceParent
15705
+ }),
15680
15706
  renderColumnStep(
15681
15707
  `${command.with_waterfall}_source`,
15682
15708
  typeof command.min_results === "number" ? `(row) => __dlContributingAliases(row, ${stableJson(aliases)})` : `(row) => __dlFirstMeaningfulAlias(row, ${stableJson(aliases)})`,
15683
- forceParent
15709
+ { recompute: forceParent }
15684
15710
  )
15685
15711
  ];
15686
15712
  }
@@ -15717,18 +15743,20 @@ function compileEnrichConfigToPlaySource(config, options = {}) {
15717
15743
  inlineRunJavascript,
15718
15744
  idiomaticGetters
15719
15745
  }),
15720
- force
15746
+ { recompute: force, recomputeOnError: true }
15721
15747
  )
15722
15748
  );
15723
15749
  });
15724
15750
  const columnStepSource = columnSteps.length > 0 ? columnSteps.join("\n") : ` .withColumn('noop', () => null)`;
15725
15751
  const metadataColumnSource = renderMetadataColumnStep(config);
15752
+ const generatedAliases = collectGeneratedAliases(config.commands);
15726
15753
  const body = [
15727
15754
  `export default definePlay(${stringLiteral(playName)}, async (ctx, input: EnrichInput) => {`,
15728
15755
  ` const sourceRows = await ctx.csv<Record<string, unknown>>(input.file);`,
15729
15756
  ` const rowStart = __dlNonNegativeInteger(input.rowStart, 0);`,
15730
15757
  ` const rowEndExclusive = Number.isFinite(input.rowEnd) ? Math.max(rowStart, __dlWholeNumber(input.rowEnd, rowStart) + 1) : undefined;`,
15731
- ` const rows = sourceRows.slice(rowStart, rowEndExclusive, { key: 'deepline_enrich_selected_rows', sourceLabel: 'Selected enrich rows' });`,
15758
+ ` const selectedRows = sourceRows.slice(rowStart, rowEndExclusive, { key: 'deepline_enrich_selected_rows', sourceLabel: 'Selected enrich rows' });`,
15759
+ ` const rows = selectedRows.map((row) => __dlStripErroredGeneratedColumns(row, ${stableJson(generatedAliases)}), { key: 'deepline_enrich_repair_rows', sourceLabel: 'Selected enrich rows' });`,
15732
15760
  ` const enriched = await ctx`,
15733
15761
  ` .dataset(${stringLiteral(mapName)}, rows)`,
15734
15762
  columnStepSource,
@@ -15814,6 +15842,18 @@ function helperSource() {
15814
15842
  ` return null;`,
15815
15843
  `}`,
15816
15844
  ``,
15845
+ `function __dlMetadataFromRow(row: Record<string, unknown>): unknown {`,
15846
+ ` const direct = __dlParseMetadata(row._metadata);`,
15847
+ ` if (direct) return direct;`,
15848
+ ` const relocated = __dlParseMetadata(row.metadata);`,
15849
+ ` if (relocated) return relocated;`,
15850
+ ` const dotted = __dlParseMetadata(row['metadata.columns']);`,
15851
+ ` if (dotted) return { columns: dotted };`,
15852
+ ` const underscored = __dlParseMetadata(row.metadata__columns);`,
15853
+ ` if (underscored) return { columns: underscored };`,
15854
+ ` return row._metadata;`,
15855
+ `}`,
15856
+ ``,
15817
15857
  `function __dlMergeMetadata(existing: unknown, patch: Record<string, unknown>): Record<string, unknown> {`,
15818
15858
  ` const base = __dlParseMetadata(existing) ?? {};`,
15819
15859
  ` const baseColumns = __dlRecord(base.columns) ? base.columns : {};`,
@@ -15888,6 +15928,40 @@ function helperSource() {
15888
15928
  ` return status === 'error' || status === 'failed' || (typeof record.error === 'string' && record.error.trim() !== '') || (typeof resultError === 'string' && resultError.trim() !== '');`,
15889
15929
  `}`,
15890
15930
  ``,
15931
+ `function __dlGeneratedCellError(value: unknown): boolean {`,
15932
+ ` if (typeof value === 'string') {`,
15933
+ ` const lower = value.trim().toLowerCase();`,
15934
+ ` if (!lower) return false;`,
15935
+ ` if (lower.startsWith('error:') || lower.includes(': error:') || lower.includes('"error"')) return true;`,
15936
+ ` try {`,
15937
+ ` return __dlGeneratedCellError(JSON.parse(value));`,
15938
+ ` } catch {`,
15939
+ ` return false;`,
15940
+ ` }`,
15941
+ ` }`,
15942
+ ` if (!__dlRecord(value)) return false;`,
15943
+ ` const status = typeof value.status === 'string' ? value.status.toLowerCase() : '';`,
15944
+ ` if (status === 'error' || status === 'failed') return true;`,
15945
+ ` if (typeof value.error === 'string' && value.error.trim() !== '') return true;`,
15946
+ ` const raw = __dlGetByPath(value, 'toolResponse.raw') ?? __dlGetByPath(value, 'toolOutput.raw');`,
15947
+ ` if (raw !== undefined && raw !== value && __dlGeneratedCellError(raw)) return true;`,
15948
+ ` const result = value.result;`,
15949
+ ` return result !== undefined && result !== value && __dlGeneratedCellError(result);`,
15950
+ `}`,
15951
+ ``,
15952
+ `function __dlStripErroredGeneratedColumns(row: Record<string, unknown>, aliases: string[]): Record<string, unknown> {`,
15953
+ ` let next: Record<string, unknown> | null = null;`,
15954
+ ` for (const alias of aliases) {`,
15955
+ ` for (const key of __dlAliasCandidates(alias)) {`,
15956
+ ` if (key in row && __dlGeneratedCellError(row[key])) {`,
15957
+ ` if (next === null) next = { ...row };`,
15958
+ ` delete next[key];`,
15959
+ ` }`,
15960
+ ` }`,
15961
+ ` }`,
15962
+ ` return next ?? row;`,
15963
+ `}`,
15964
+ ``,
15891
15965
  `function __dlRawToolOutput(result: unknown): unknown {`,
15892
15966
  ` if (!result || typeof result !== 'object') return result;`,
15893
15967
  ` const record = result as Record<string, unknown>;`,
@@ -16298,7 +16372,7 @@ function helperSource() {
16298
16372
  ` tool: input.tool,`,
16299
16373
  ` input: payload,`,
16300
16374
  ` ...(input.description ? { description: input.description } : {}),`,
16301
- ` ...(input.force ? { staleAfterSeconds: 1 } : {}),`,
16375
+ ` ...(input.force ? { force: true } : {}),`,
16302
16376
  ` });`,
16303
16377
  ` return __dlExtract(input.alias, result, input.row, input.extract, Boolean(input.legacyEnvelope));`,
16304
16378
  `}`
package/dist/index.d.mts CHANGED
@@ -70,6 +70,8 @@ interface PlayStaticColumnProducer {
70
70
  toolId?: string;
71
71
  playId?: string;
72
72
  dependsOnFields?: string[];
73
+ recompute?: boolean;
74
+ recomputeOnError?: boolean;
73
75
  staleAfterSeconds?: number;
74
76
  conditional?: boolean;
75
77
  sourceRange?: PlayStaticSourceRange;
@@ -80,6 +82,8 @@ interface PlayStaticDatasetColumn {
80
82
  id: string;
81
83
  source: PlaySheetColumnSource;
82
84
  sqlName?: string;
85
+ recompute?: boolean;
86
+ recomputeOnError?: boolean;
83
87
  staleAfterSeconds?: number;
84
88
  producers: PlayStaticColumnProducer[];
85
89
  }
@@ -93,6 +97,8 @@ interface PlayStaticSourceRange {
93
97
  type PlayStaticSubstepMetadata = {
94
98
  conditional?: boolean;
95
99
  dependsOnFields?: string[];
100
+ recompute?: boolean;
101
+ recomputeOnError?: boolean;
96
102
  staleAfterSeconds?: number;
97
103
  };
98
104
  type PlayStaticSubstep = PlayStaticSubstepMetadata & ({
@@ -2979,6 +2985,8 @@ type ToolExecutionRequest = {
2979
2985
  input: Record<string, unknown>;
2980
2986
  /** Human-readable description for logs and run inspection. */
2981
2987
  description?: string;
2988
+ /** Recompute this tool call instead of reusing a durable receipt/checkpoint. */
2989
+ force?: boolean;
2982
2990
  /** Numeric TTL in seconds for this tool checkpoint. */
2983
2991
  staleAfterSeconds?: number;
2984
2992
  };
@@ -3031,6 +3039,10 @@ type DatasetColumnDefinition<Row, Value> = {
3031
3039
  run: (input: DatasetColumnRunInput<Row, Value>) => Value | Promise<Value>;
3032
3040
  /** Optional row-level gate. Skipped rows produce `null` for this column. */
3033
3041
  readonly runIf?: (row: Row, index: number) => boolean | Promise<boolean>;
3042
+ /** Recompute this cell on each run instead of reusing its durable value. */
3043
+ readonly recompute?: boolean;
3044
+ /** Recompute this cell when its durable value is an error-shaped object. */
3045
+ readonly recomputeOnError?: boolean;
3034
3046
  /** Fixed or value-dependent freshness policy for this cell. */
3035
3047
  readonly staleAfterSeconds?: StaleAfterSeconds<Value>;
3036
3048
  };
@@ -3049,6 +3061,10 @@ type ConditionalStepResolver<Row, Value, Else = null> = {
3049
3061
  type StepOptions<Row, Value = unknown> = {
3050
3062
  /** Optional row-level gate. Skipped rows produce `null` for this column. */
3051
3063
  readonly runIf?: (row: Row, index: number) => boolean | Promise<boolean>;
3064
+ /** Recompute this cell on each run instead of reusing its durable value. */
3065
+ readonly recompute?: boolean;
3066
+ /** Recompute this cell when its durable value is an error-shaped object. */
3067
+ readonly recomputeOnError?: boolean;
3052
3068
  /** Fixed or value-dependent freshness policy for this cell. */
3053
3069
  readonly staleAfterSeconds?: StaleAfterSeconds<Value>;
3054
3070
  };
@@ -3069,11 +3085,13 @@ type StepProgramResolver<Input, Return> = {
3069
3085
  };
3070
3086
  type PlayStepProgramStep = {
3071
3087
  readonly name: string;
3088
+ readonly recompute?: boolean;
3089
+ readonly recomputeOnError?: boolean;
3072
3090
  readonly staleAfterSeconds?: StaleAfterSeconds;
3073
3091
  readonly resolver: StepResolver<Record<string, unknown>, unknown> | ConditionalStepResolver<Record<string, unknown>, unknown> | StepProgramResolver<Record<string, unknown>, unknown>;
3074
3092
  };
3075
3093
  type ColumnResolver<Row, Value> = StepResolver<Row, Value> | ConditionalStepResolver<Row, Value> | StepProgramResolver<Row, Value>;
3076
- type StepProgramOutput<TProgram> = TProgram extends StepProgram<any, infer Output, any> ? Output : never;
3094
+ type StepProgramOutput<TProgram> = TProgram extends StepProgram<unknown, infer Output, unknown> ? Output : never;
3077
3095
  /**
3078
3096
  * Builder returned by `ctx.dataset(...)` for row-level durable columns.
3079
3097
  *
@@ -3315,9 +3333,7 @@ interface DeeplinePlayRuntimeContext {
3315
3333
  *
3316
3334
  * @sdkReference runtime 150 ctx.tools.execute(request)
3317
3335
  */
3318
- execute<TOutput = LoosePlayObject>(request: ToolExecutionRequest & {
3319
- staleAfterSeconds?: number;
3320
- }): Promise<ToolExecuteResult<TOutput>>;
3336
+ execute<TOutput = LoosePlayObject>(request: ToolExecutionRequest): Promise<ToolExecuteResult<TOutput>>;
3321
3337
  };
3322
3338
  /**
3323
3339
  * Execute a single tool by stable step key and tool ID.
@@ -3343,7 +3359,7 @@ interface DeeplinePlayRuntimeContext {
3343
3359
  *
3344
3360
  * @sdkReference runtime 180 ctx.runSteps(program, input, options)
3345
3361
  */
3346
- runSteps<TInput extends Record<string, unknown>, TOutput>(program: StepProgram<TInput, any, TOutput>, input: TInput, options?: {
3362
+ runSteps<TInput extends Record<string, unknown>, TOutput>(program: StepProgram<TInput, unknown, TOutput>, input: TInput, options?: {
3347
3363
  description?: string;
3348
3364
  }): Promise<TOutput>;
3349
3365
  /**
package/dist/index.d.ts CHANGED
@@ -70,6 +70,8 @@ interface PlayStaticColumnProducer {
70
70
  toolId?: string;
71
71
  playId?: string;
72
72
  dependsOnFields?: string[];
73
+ recompute?: boolean;
74
+ recomputeOnError?: boolean;
73
75
  staleAfterSeconds?: number;
74
76
  conditional?: boolean;
75
77
  sourceRange?: PlayStaticSourceRange;
@@ -80,6 +82,8 @@ interface PlayStaticDatasetColumn {
80
82
  id: string;
81
83
  source: PlaySheetColumnSource;
82
84
  sqlName?: string;
85
+ recompute?: boolean;
86
+ recomputeOnError?: boolean;
83
87
  staleAfterSeconds?: number;
84
88
  producers: PlayStaticColumnProducer[];
85
89
  }
@@ -93,6 +97,8 @@ interface PlayStaticSourceRange {
93
97
  type PlayStaticSubstepMetadata = {
94
98
  conditional?: boolean;
95
99
  dependsOnFields?: string[];
100
+ recompute?: boolean;
101
+ recomputeOnError?: boolean;
96
102
  staleAfterSeconds?: number;
97
103
  };
98
104
  type PlayStaticSubstep = PlayStaticSubstepMetadata & ({
@@ -2979,6 +2985,8 @@ type ToolExecutionRequest = {
2979
2985
  input: Record<string, unknown>;
2980
2986
  /** Human-readable description for logs and run inspection. */
2981
2987
  description?: string;
2988
+ /** Recompute this tool call instead of reusing a durable receipt/checkpoint. */
2989
+ force?: boolean;
2982
2990
  /** Numeric TTL in seconds for this tool checkpoint. */
2983
2991
  staleAfterSeconds?: number;
2984
2992
  };
@@ -3031,6 +3039,10 @@ type DatasetColumnDefinition<Row, Value> = {
3031
3039
  run: (input: DatasetColumnRunInput<Row, Value>) => Value | Promise<Value>;
3032
3040
  /** Optional row-level gate. Skipped rows produce `null` for this column. */
3033
3041
  readonly runIf?: (row: Row, index: number) => boolean | Promise<boolean>;
3042
+ /** Recompute this cell on each run instead of reusing its durable value. */
3043
+ readonly recompute?: boolean;
3044
+ /** Recompute this cell when its durable value is an error-shaped object. */
3045
+ readonly recomputeOnError?: boolean;
3034
3046
  /** Fixed or value-dependent freshness policy for this cell. */
3035
3047
  readonly staleAfterSeconds?: StaleAfterSeconds<Value>;
3036
3048
  };
@@ -3049,6 +3061,10 @@ type ConditionalStepResolver<Row, Value, Else = null> = {
3049
3061
  type StepOptions<Row, Value = unknown> = {
3050
3062
  /** Optional row-level gate. Skipped rows produce `null` for this column. */
3051
3063
  readonly runIf?: (row: Row, index: number) => boolean | Promise<boolean>;
3064
+ /** Recompute this cell on each run instead of reusing its durable value. */
3065
+ readonly recompute?: boolean;
3066
+ /** Recompute this cell when its durable value is an error-shaped object. */
3067
+ readonly recomputeOnError?: boolean;
3052
3068
  /** Fixed or value-dependent freshness policy for this cell. */
3053
3069
  readonly staleAfterSeconds?: StaleAfterSeconds<Value>;
3054
3070
  };
@@ -3069,11 +3085,13 @@ type StepProgramResolver<Input, Return> = {
3069
3085
  };
3070
3086
  type PlayStepProgramStep = {
3071
3087
  readonly name: string;
3088
+ readonly recompute?: boolean;
3089
+ readonly recomputeOnError?: boolean;
3072
3090
  readonly staleAfterSeconds?: StaleAfterSeconds;
3073
3091
  readonly resolver: StepResolver<Record<string, unknown>, unknown> | ConditionalStepResolver<Record<string, unknown>, unknown> | StepProgramResolver<Record<string, unknown>, unknown>;
3074
3092
  };
3075
3093
  type ColumnResolver<Row, Value> = StepResolver<Row, Value> | ConditionalStepResolver<Row, Value> | StepProgramResolver<Row, Value>;
3076
- type StepProgramOutput<TProgram> = TProgram extends StepProgram<any, infer Output, any> ? Output : never;
3094
+ type StepProgramOutput<TProgram> = TProgram extends StepProgram<unknown, infer Output, unknown> ? Output : never;
3077
3095
  /**
3078
3096
  * Builder returned by `ctx.dataset(...)` for row-level durable columns.
3079
3097
  *
@@ -3315,9 +3333,7 @@ interface DeeplinePlayRuntimeContext {
3315
3333
  *
3316
3334
  * @sdkReference runtime 150 ctx.tools.execute(request)
3317
3335
  */
3318
- execute<TOutput = LoosePlayObject>(request: ToolExecutionRequest & {
3319
- staleAfterSeconds?: number;
3320
- }): Promise<ToolExecuteResult<TOutput>>;
3336
+ execute<TOutput = LoosePlayObject>(request: ToolExecutionRequest): Promise<ToolExecuteResult<TOutput>>;
3321
3337
  };
3322
3338
  /**
3323
3339
  * Execute a single tool by stable step key and tool ID.
@@ -3343,7 +3359,7 @@ interface DeeplinePlayRuntimeContext {
3343
3359
  *
3344
3360
  * @sdkReference runtime 180 ctx.runSteps(program, input, options)
3345
3361
  */
3346
- runSteps<TInput extends Record<string, unknown>, TOutput>(program: StepProgram<TInput, any, TOutput>, input: TInput, options?: {
3362
+ runSteps<TInput extends Record<string, unknown>, TOutput>(program: StepProgram<TInput, unknown, TOutput>, input: TInput, options?: {
3347
3363
  description?: string;
3348
3364
  }): Promise<TOutput>;
3349
3365
  /**
package/dist/index.js CHANGED
@@ -272,16 +272,18 @@ var SDK_RELEASE = {
272
272
  // 0.1.106 ships play cell provenance metadata and v2 preview retry hardening.
273
273
  // 0.1.107 ships the v2 quickstart command, the deepline-plays-quickstart
274
274
  // skill on the sdk sync surface, and the people-search-to-email prebuilt.
275
- version: "0.1.107",
275
+ // 0.1.108 ships explicit dataset column/tool recompute policy and removes
276
+ // the SDK enrich generator's one-second stale policy.
277
+ version: "0.1.108",
276
278
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
277
279
  supportPolicy: {
278
- latest: "0.1.107",
280
+ latest: "0.1.108",
279
281
  minimumSupported: "0.1.53",
280
282
  deprecatedBelow: "0.1.53",
281
283
  commandMinimumSupported: [
282
284
  {
283
285
  command: "enrich",
284
- minimumSupported: "0.1.106",
286
+ minimumSupported: "0.1.108",
285
287
  reason: "Older SDK CLI enrich generated stale play source for the current dataset API."
286
288
  }
287
289
  ],
@@ -885,6 +887,8 @@ function normalizeStepProgress(value) {
885
887
  value.artifactTableNamespace
886
888
  )
887
889
  } : {},
890
+ ...finiteNumber(value.startedAt) !== null ? { startedAt: finiteNumber(value.startedAt) } : {},
891
+ ...finiteNumber(value.completedAt) !== null ? { completedAt: finiteNumber(value.completedAt) } : {},
888
892
  ...finiteNumber(value.updatedAt) !== null ? { updatedAt: finiteNumber(value.updatedAt) } : {}
889
893
  };
890
894
  }
@@ -4075,7 +4079,9 @@ var DeeplineStepProgram = class _DeeplineStepProgram {
4075
4079
  resolver: stepResolver,
4076
4080
  ...options?.staleAfterSeconds !== void 0 ? {
4077
4081
  staleAfterSeconds: options.staleAfterSeconds
4078
- } : {}
4082
+ } : {},
4083
+ ...options?.recompute === true ? { recompute: true } : {},
4084
+ ...options?.recomputeOnError === true ? { recomputeOnError: true } : {}
4079
4085
  }
4080
4086
  ],
4081
4087
  this.returnResolver
package/dist/index.mjs CHANGED
@@ -194,16 +194,18 @@ var SDK_RELEASE = {
194
194
  // 0.1.106 ships play cell provenance metadata and v2 preview retry hardening.
195
195
  // 0.1.107 ships the v2 quickstart command, the deepline-plays-quickstart
196
196
  // skill on the sdk sync surface, and the people-search-to-email prebuilt.
197
- version: "0.1.107",
197
+ // 0.1.108 ships explicit dataset column/tool recompute policy and removes
198
+ // the SDK enrich generator's one-second stale policy.
199
+ version: "0.1.108",
198
200
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
199
201
  supportPolicy: {
200
- latest: "0.1.107",
202
+ latest: "0.1.108",
201
203
  minimumSupported: "0.1.53",
202
204
  deprecatedBelow: "0.1.53",
203
205
  commandMinimumSupported: [
204
206
  {
205
207
  command: "enrich",
206
- minimumSupported: "0.1.106",
208
+ minimumSupported: "0.1.108",
207
209
  reason: "Older SDK CLI enrich generated stale play source for the current dataset API."
208
210
  }
209
211
  ],
@@ -807,6 +809,8 @@ function normalizeStepProgress(value) {
807
809
  value.artifactTableNamespace
808
810
  )
809
811
  } : {},
812
+ ...finiteNumber(value.startedAt) !== null ? { startedAt: finiteNumber(value.startedAt) } : {},
813
+ ...finiteNumber(value.completedAt) !== null ? { completedAt: finiteNumber(value.completedAt) } : {},
810
814
  ...finiteNumber(value.updatedAt) !== null ? { updatedAt: finiteNumber(value.updatedAt) } : {}
811
815
  };
812
816
  }
@@ -3997,7 +4001,9 @@ var DeeplineStepProgram = class _DeeplineStepProgram {
3997
4001
  resolver: stepResolver,
3998
4002
  ...options?.staleAfterSeconds !== void 0 ? {
3999
4003
  staleAfterSeconds: options.staleAfterSeconds
4000
- } : {}
4004
+ } : {},
4005
+ ...options?.recompute === true ? { recompute: true } : {},
4006
+ ...options?.recomputeOnError === true ? { recomputeOnError: true } : {}
4001
4007
  }
4002
4008
  ],
4003
4009
  this.returnResolver
@@ -1959,6 +1959,8 @@ type WorkerConditionalStepResolver = {
1959
1959
 
1960
1960
  type WorkerStepProgramStep = {
1961
1961
  name: string;
1962
+ recompute?: boolean;
1963
+ recomputeOnError?: boolean;
1962
1964
  staleAfterSeconds?: AuthoredStaleAfterSeconds;
1963
1965
  resolver:
1964
1966
  | WorkerStepResolver
@@ -1993,6 +1995,49 @@ type WorkerMapOptions = {
1993
1995
  onRowError?: 'isolate' | 'fail';
1994
1996
  };
1995
1997
 
1998
+ function authoredCellPolicyForWorkerStep(
1999
+ step: WorkerStepProgramStep,
2000
+ ): {
2001
+ recompute?: true;
2002
+ recomputeOnError?: true;
2003
+ staleAfterSeconds?: AuthoredStaleAfterSeconds;
2004
+ } | null {
2005
+ const policy = {
2006
+ ...(step.recompute === true ? { recompute: true as const } : {}),
2007
+ ...(step.recomputeOnError === true
2008
+ ? { recomputeOnError: true as const }
2009
+ : {}),
2010
+ ...(step.staleAfterSeconds !== undefined
2011
+ ? { staleAfterSeconds: step.staleAfterSeconds }
2012
+ : {}),
2013
+ };
2014
+ return Object.keys(policy).length > 0 ? policy : null;
2015
+ }
2016
+
2017
+ function workerCellPoliciesFromSteps(
2018
+ steps: readonly WorkerStepProgramStep[],
2019
+ ): CellStalenessPolicyByField {
2020
+ return Object.fromEntries(
2021
+ steps.flatMap((step) => {
2022
+ const policy = authoredCellPolicyForWorkerStep(step);
2023
+ return policy
2024
+ ? [[step.name, normalizeCellStalenessPolicy(policy)]]
2025
+ : [];
2026
+ }),
2027
+ ) as CellStalenessPolicyByField;
2028
+ }
2029
+
2030
+ function authoredWorkerCellPoliciesFromSteps(
2031
+ steps: readonly WorkerStepProgramStep[],
2032
+ ): AuthoredCellStalenessPolicyByField {
2033
+ return Object.fromEntries(
2034
+ steps.flatMap((step) => {
2035
+ const policy = authoredCellPolicyForWorkerStep(step);
2036
+ return policy ? [[step.name, policy]] : [];
2037
+ }),
2038
+ ) as AuthoredCellStalenessPolicyByField;
2039
+ }
2040
+
1996
2041
  /**
1997
2042
  * Per-cell terminal state recorded by map row execution and merged into the
1998
2043
  * Runtime Sheet row's `_cell_meta`. 'failed' carries the cell's error message;
@@ -2983,8 +3028,8 @@ function augmentSheetContractWithDatasetFields(input: {
2983
3028
  const field = typeof column.field === 'string' ? column.field : column.id;
2984
3029
  if (
2985
3030
  column.source === 'input' &&
2986
- field === input.contract.tableNamespace &&
2987
- !candidateFields.has(field)
3031
+ ((field === input.contract.tableNamespace && !candidateFields.has(field)) ||
3032
+ outputFields.has(field))
2988
3033
  ) {
2989
3034
  continue;
2990
3035
  }
@@ -4696,27 +4741,10 @@ function createMinimalWorkerCtx(
4696
4741
  const fields = Object.fromEntries(
4697
4742
  program.steps.map((step) => [step.name, step.resolver]),
4698
4743
  );
4699
- const cellPolicies = Object.fromEntries(
4700
- program.steps.flatMap((step) =>
4701
- step.staleAfterSeconds === undefined
4702
- ? []
4703
- : [
4704
- [
4705
- step.name,
4706
- normalizeCellStalenessPolicy({
4707
- staleAfterSeconds: step.staleAfterSeconds,
4708
- }),
4709
- ],
4710
- ],
4711
- ),
4712
- ) as CellStalenessPolicyByField;
4713
- const authoredCellPolicies = Object.fromEntries(
4714
- program.steps.flatMap((step) =>
4715
- step.staleAfterSeconds === undefined
4716
- ? []
4717
- : [[step.name, { staleAfterSeconds: step.staleAfterSeconds }]],
4718
- ),
4719
- ) as AuthoredCellStalenessPolicyByField;
4744
+ const cellPolicies = workerCellPoliciesFromSteps(program.steps);
4745
+ const authoredCellPolicies = authoredWorkerCellPoliciesFromSteps(
4746
+ program.steps,
4747
+ );
4720
4748
  return runMap(
4721
4749
  this.name,
4722
4750
  this.rows,
@@ -4944,27 +4972,10 @@ function createMinimalWorkerCtx(
4944
4972
  const fields = Object.fromEntries(
4945
4973
  fieldsDef.steps.map((step) => [step.name, step.resolver]),
4946
4974
  );
4947
- const cellPolicies = Object.fromEntries(
4948
- fieldsDef.steps.flatMap((step) =>
4949
- step.staleAfterSeconds === undefined
4950
- ? []
4951
- : [
4952
- [
4953
- step.name,
4954
- normalizeCellStalenessPolicy({
4955
- staleAfterSeconds: step.staleAfterSeconds,
4956
- }),
4957
- ],
4958
- ],
4959
- ),
4960
- ) as CellStalenessPolicyByField;
4961
- const authoredCellPolicies = Object.fromEntries(
4962
- fieldsDef.steps.flatMap((step) =>
4963
- step.staleAfterSeconds === undefined
4964
- ? []
4965
- : [[step.name, { staleAfterSeconds: step.staleAfterSeconds }]],
4966
- ),
4967
- ) as AuthoredCellStalenessPolicyByField;
4975
+ const cellPolicies = workerCellPoliciesFromSteps(fieldsDef.steps);
4976
+ const authoredCellPolicies = authoredWorkerCellPoliciesFromSteps(
4977
+ fieldsDef.steps,
4978
+ );
4968
4979
  return runMap(
4969
4980
  name,
4970
4981
  rows,
@@ -231,6 +231,8 @@ export type ToolExecutionRequest = {
231
231
  input: Record<string, unknown>;
232
232
  /** Human-readable description for logs and run inspection. */
233
233
  description?: string;
234
+ /** Recompute this tool call instead of reusing a durable receipt/checkpoint. */
235
+ force?: boolean;
234
236
  /** Numeric TTL in seconds for this tool checkpoint. */
235
237
  staleAfterSeconds?: number;
236
238
  };
@@ -294,6 +296,10 @@ export type DatasetColumnDefinition<Row, Value> = {
294
296
  run: (input: DatasetColumnRunInput<Row, Value>) => Value | Promise<Value>;
295
297
  /** Optional row-level gate. Skipped rows produce `null` for this column. */
296
298
  readonly runIf?: (row: Row, index: number) => boolean | Promise<boolean>;
299
+ /** Recompute this cell on each run instead of reusing its durable value. */
300
+ readonly recompute?: boolean;
301
+ /** Recompute this cell when its durable value is an error-shaped object. */
302
+ readonly recomputeOnError?: boolean;
297
303
  /** Fixed or value-dependent freshness policy for this cell. */
298
304
  readonly staleAfterSeconds?: StaleAfterSeconds<Value>;
299
305
  };
@@ -316,6 +322,10 @@ export type ConditionalStepResolver<Row, Value, Else = null> = {
316
322
  export type StepOptions<Row, Value = unknown> = {
317
323
  /** Optional row-level gate. Skipped rows produce `null` for this column. */
318
324
  readonly runIf?: (row: Row, index: number) => boolean | Promise<boolean>;
325
+ /** Recompute this cell on each run instead of reusing its durable value. */
326
+ readonly recompute?: boolean;
327
+ /** Recompute this cell when its durable value is an error-shaped object. */
328
+ readonly recomputeOnError?: boolean;
319
329
  /** Fixed or value-dependent freshness policy for this cell. */
320
330
  readonly staleAfterSeconds?: StaleAfterSeconds<Value>;
321
331
  };
@@ -351,6 +361,8 @@ export type StepProgramResolver<Input, Return> = {
351
361
 
352
362
  export type PlayStepProgramStep = {
353
363
  readonly name: string;
364
+ readonly recompute?: boolean;
365
+ readonly recomputeOnError?: boolean;
354
366
  readonly staleAfterSeconds?: StaleAfterSeconds;
355
367
  readonly resolver:
356
368
  | StepResolver<Record<string, unknown>, unknown>
@@ -364,7 +376,7 @@ export type ColumnResolver<Row, Value> =
364
376
  | StepProgramResolver<Row, Value>;
365
377
 
366
378
  export type StepProgramOutput<TProgram> =
367
- TProgram extends StepProgram<any, infer Output, any> ? Output : never;
379
+ TProgram extends StepProgram<unknown, infer Output, unknown> ? Output : never;
368
380
 
369
381
  /**
370
382
  * Builder returned by `ctx.dataset(...)` for row-level durable columns.
@@ -663,7 +675,7 @@ export interface DeeplinePlayRuntimeContext {
663
675
  * @sdkReference runtime 150 ctx.tools.execute(request)
664
676
  */
665
677
  execute<TOutput = LoosePlayObject>(
666
- request: ToolExecutionRequest & { staleAfterSeconds?: number },
678
+ request: ToolExecutionRequest,
667
679
  ): Promise<ToolExecuteResult<TOutput>>;
668
680
  };
669
681
  /**
@@ -694,7 +706,7 @@ export interface DeeplinePlayRuntimeContext {
694
706
  * @sdkReference runtime 180 ctx.runSteps(program, input, options)
695
707
  */
696
708
  runSteps<TInput extends Record<string, unknown>, TOutput>(
697
- program: StepProgram<TInput, any, TOutput>,
709
+ program: StepProgram<TInput, unknown, TOutput>,
698
710
  input: TInput,
699
711
  options?: { description?: string },
700
712
  ): Promise<TOutput>;
@@ -1128,6 +1140,10 @@ class DeeplineStepProgram<Input, Output, ReturnValue> implements StepProgram<
1128
1140
  options.staleAfterSeconds as PlayStepProgramStep['staleAfterSeconds'],
1129
1141
  }
1130
1142
  : {}),
1143
+ ...(options?.recompute === true ? { recompute: true } : {}),
1144
+ ...(options?.recomputeOnError === true
1145
+ ? { recomputeOnError: true }
1146
+ : {}),
1131
1147
  },
1132
1148
  ],
1133
1149
  this.returnResolver as StepResolver<
@@ -1598,7 +1614,8 @@ function emailStatusExtractorConfig(
1598
1614
  if (paths) config[key] = paths;
1599
1615
  }
1600
1616
  if (isRecord(value.statusMap)) {
1601
- config.statusMap = value.statusMap as EmailStatusExtractorConfig['statusMap'];
1617
+ config.statusMap =
1618
+ value.statusMap as EmailStatusExtractorConfig['statusMap'];
1602
1619
  }
1603
1620
  if (Array.isArray(value.rules)) {
1604
1621
  config.rules = value.rules as EmailStatusExtractorConfig['rules'];
@@ -94,16 +94,18 @@ export const SDK_RELEASE = {
94
94
  // 0.1.106 ships play cell provenance metadata and v2 preview retry hardening.
95
95
  // 0.1.107 ships the v2 quickstart command, the deepline-plays-quickstart
96
96
  // skill on the sdk sync surface, and the people-search-to-email prebuilt.
97
- version: '0.1.107',
97
+ // 0.1.108 ships explicit dataset column/tool recompute policy and removes
98
+ // the SDK enrich generator's one-second stale policy.
99
+ version: '0.1.108',
98
100
  apiContract: '2026-06-dataset-column-cell-stale-hard-cutover',
99
101
  supportPolicy: {
100
- latest: '0.1.107',
102
+ latest: '0.1.108',
101
103
  minimumSupported: '0.1.53',
102
104
  deprecatedBelow: '0.1.53',
103
105
  commandMinimumSupported: [
104
106
  {
105
107
  command: 'enrich',
106
- minimumSupported: '0.1.106',
108
+ minimumSupported: '0.1.108',
107
109
  reason:
108
110
  'Older SDK CLI enrich generated stale play source for the current dataset API.',
109
111
  },
@@ -134,6 +134,10 @@ class WorkerStepProgram<Input, Output, ReturnValue> implements StepProgram<
134
134
  options.staleAfterSeconds as PlayStepProgramStep['staleAfterSeconds'],
135
135
  }
136
136
  : {}),
137
+ ...(options?.recompute === true ? { recompute: true } : {}),
138
+ ...(options?.recomputeOnError === true
139
+ ? { recomputeOnError: true }
140
+ : {}),
137
141
  },
138
142
  ],
139
143
  this.returnResolver as StepResolver<
@@ -15,11 +15,15 @@ export type AuthoredStaleAfterSeconds<Value = unknown> =
15
15
 
16
16
  /** Freshness policy as written by play authors before runtime normalization. */
17
17
  export type AuthoredCellStalenessPolicy<Value = unknown> = {
18
+ recompute?: boolean;
19
+ recomputeOnError?: boolean;
18
20
  staleAfterSeconds?: AuthoredStaleAfterSeconds<Value>;
19
21
  };
20
22
 
21
23
  /** Runtime-normalized freshness policy stored in execution plans. */
22
24
  export type CellStalenessPolicy = {
25
+ recompute?: boolean;
26
+ recomputeOnError?: boolean;
23
27
  staleAfterSeconds?: number;
24
28
  dynamicStaleAfterSeconds?: boolean;
25
29
  };
@@ -53,7 +57,10 @@ export type PreviousCell<Value = unknown> = {
53
57
  };
54
58
 
55
59
  export type CellStalenessDecision =
56
- | { action: 'recompute'; reason: 'missing' | 'failed' | 'stale' }
60
+ | {
61
+ action: 'recompute';
62
+ reason: 'missing' | 'failed' | 'forced' | 'stale';
63
+ }
57
64
  | { action: 'reuse'; reason: 'fresh' | 'no_policy' | 'no_expiry' };
58
65
 
59
66
  export type CellStalenessPolicyByField = Record<string, CellStalenessPolicy>;
@@ -83,15 +90,36 @@ export function validateStaleAfterSeconds(
83
90
  export function normalizeCellStalenessPolicy(
84
91
  policy: AuthoredCellStalenessPolicy | undefined,
85
92
  ): CellStalenessPolicy {
93
+ if (
94
+ policy?.recompute !== undefined &&
95
+ typeof policy.recompute !== 'boolean'
96
+ ) {
97
+ throw new Error('recompute must be a boolean.');
98
+ }
99
+ if (
100
+ policy?.recomputeOnError !== undefined &&
101
+ typeof policy.recomputeOnError !== 'boolean'
102
+ ) {
103
+ throw new Error('recomputeOnError must be a boolean.');
104
+ }
105
+ const recompute = policy?.recompute === true;
106
+ const recomputeOnError = policy?.recomputeOnError === true;
107
+ const flags = {
108
+ ...(recompute ? { recompute: true } : {}),
109
+ ...(recomputeOnError ? { recomputeOnError: true } : {}),
110
+ };
86
111
  const staleAfterSeconds = policy?.staleAfterSeconds;
87
112
  if (staleAfterSeconds === undefined) {
88
- return {};
113
+ return flags;
89
114
  }
90
115
  if (typeof staleAfterSeconds === 'function') {
91
- return { dynamicStaleAfterSeconds: true };
116
+ return {
117
+ ...flags,
118
+ dynamicStaleAfterSeconds: true,
119
+ };
92
120
  }
93
121
  validateStaleAfterSeconds(staleAfterSeconds);
94
- return { staleAfterSeconds };
122
+ return { ...flags, staleAfterSeconds };
95
123
  }
96
124
 
97
125
  export function resolveCompletedCellStalenessMeta<Value>(input: {
@@ -162,6 +190,7 @@ export function previousCellFromValue<Value>(input: {
162
190
 
163
191
  export function shouldRecomputeCell(input: {
164
192
  hasValue: boolean;
193
+ value?: unknown;
165
194
  meta?: CellStalenessMeta | null;
166
195
  policy?: CellStalenessPolicy;
167
196
  nowMs?: number;
@@ -175,6 +204,17 @@ export function shouldRecomputeCell(input: {
175
204
  return { action: 'recompute', reason: 'failed' };
176
205
  }
177
206
 
207
+ if (input.policy?.recompute === true) {
208
+ return { action: 'recompute', reason: 'forced' };
209
+ }
210
+
211
+ if (
212
+ input.policy?.recomputeOnError === true &&
213
+ isErrorLikeCellValue(input.value)
214
+ ) {
215
+ return { action: 'recompute', reason: 'failed' };
216
+ }
217
+
178
218
  const staleAt =
179
219
  input.meta && Object.prototype.hasOwnProperty.call(input.meta, 'staleAt')
180
220
  ? input.meta.staleAt
@@ -254,7 +294,26 @@ export function cellPolicyFields(
254
294
  .filter(
255
295
  ([, policy]) =>
256
296
  policy.staleAfterSeconds !== undefined ||
257
- policy.dynamicStaleAfterSeconds === true,
297
+ policy.dynamicStaleAfterSeconds === true ||
298
+ policy.recompute === true ||
299
+ policy.recomputeOnError === true,
258
300
  )
259
301
  .map(([field]) => field);
260
302
  }
303
+
304
+ function isErrorLikeCellValue(value: unknown): boolean {
305
+ if (typeof value === 'string') {
306
+ return /(^|: )error(:|$)/i.test(value.trim());
307
+ }
308
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
309
+ return false;
310
+ }
311
+ const record = value as Record<string, unknown>;
312
+ const raw = (record.toolResponse as { raw?: unknown } | undefined)?.raw;
313
+ return (
314
+ (raw !== undefined && isErrorLikeCellValue(raw)) ||
315
+ (typeof record.error === 'string' && record.error.trim().length > 0) ||
316
+ record.status === 'failed' ||
317
+ record.status === 'error'
318
+ );
319
+ }
@@ -438,6 +438,12 @@ function normalizeStepProgress(
438
438
  ),
439
439
  }
440
440
  : {}),
441
+ ...(finiteNumber(value.startedAt) !== null
442
+ ? { startedAt: finiteNumber(value.startedAt) }
443
+ : {}),
444
+ ...(finiteNumber(value.completedAt) !== null
445
+ ? { completedAt: finiteNumber(value.completedAt) }
446
+ : {}),
441
447
  ...(finiteNumber(value.updatedAt) !== null
442
448
  ? { updatedAt: finiteNumber(value.updatedAt) }
443
449
  : {}),
@@ -5,6 +5,8 @@ export type StepProgramDatasetOptions = {
5
5
  row: Record<string, unknown>,
6
6
  index: number,
7
7
  ) => boolean | Promise<boolean>;
8
+ recompute?: boolean;
9
+ recomputeOnError?: boolean;
8
10
  staleAfterSeconds?: AuthoredStaleAfterSeconds;
9
11
  };
10
12
 
@@ -48,6 +50,8 @@ function isPreviousCell(value: unknown): value is PreviousCell {
48
50
 
49
51
  export type StepProgramDatasetStep<TResolver> = {
50
52
  name: string;
53
+ recompute?: boolean;
54
+ recomputeOnError?: boolean;
51
55
  resolver: TResolver;
52
56
  staleAfterSeconds?: AuthoredStaleAfterSeconds;
53
57
  };
@@ -133,6 +137,10 @@ export class StepProgramDatasetBuilder<
133
137
  ...(normalized.options?.staleAfterSeconds !== undefined
134
138
  ? { staleAfterSeconds: normalized.options.staleAfterSeconds }
135
139
  : {}),
140
+ ...(normalized.options?.recompute === true ? { recompute: true } : {}),
141
+ ...(normalized.options?.recomputeOnError === true
142
+ ? { recomputeOnError: true }
143
+ : {}),
136
144
  } as TStep,
137
145
  ];
138
146
  return this;
@@ -32,6 +32,61 @@ export interface PlayStaticPipeline {
32
32
  sheetContractErrors?: string[];
33
33
  }
34
34
 
35
+ function dedupeStaticFieldNames(
36
+ fields: Array<string | null | undefined>,
37
+ ): string[] {
38
+ const seen = new Set<string>();
39
+ const out: string[] = [];
40
+ for (const field of fields) {
41
+ const trimmed = typeof field === 'string' ? field.trim() : '';
42
+ if (!trimmed || seen.has(trimmed)) {
43
+ continue;
44
+ }
45
+ seen.add(trimmed);
46
+ out.push(trimmed);
47
+ }
48
+ return out;
49
+ }
50
+
51
+ export function inputFieldFromStaticCsvArg(csvArg: unknown): string | null {
52
+ if (typeof csvArg !== 'string') return null;
53
+ const match = /^input\.([A-Za-z_$][\w$]*)$/.exec(csvArg.trim());
54
+ return match?.[1] ?? null;
55
+ }
56
+
57
+ export function deriveStaticPipelineFileInputFields(
58
+ pipeline: PlayStaticPipeline | null | undefined,
59
+ ): string[] {
60
+ if (!pipeline) {
61
+ return [];
62
+ }
63
+ const explicitCsvFields = getCompiledPipelineSubsteps(pipeline)
64
+ .filter((substep) => substep.type === 'csv')
65
+ .map((substep) =>
66
+ substep.type === 'csv'
67
+ ? (inputFieldFromStaticCsvArg(substep.path) ?? substep.field)
68
+ : null,
69
+ );
70
+ const inferredCsvField =
71
+ explicitCsvFields.length > 0
72
+ ? null
73
+ : (inputFieldFromStaticCsvArg(pipeline.csvArg) ??
74
+ (pipeline.csvArg ? 'csv' : null));
75
+ return dedupeStaticFieldNames([...explicitCsvFields, inferredCsvField]);
76
+ }
77
+
78
+ export function deriveStaticPipelineEntryInputFields(
79
+ pipeline: PlayStaticPipeline | null | undefined,
80
+ ): string[] {
81
+ if (!pipeline) {
82
+ return [];
83
+ }
84
+ const fileInputs = deriveStaticPipelineFileInputFields(pipeline);
85
+ return fileInputs.length > 0
86
+ ? fileInputs
87
+ : dedupeStaticFieldNames(pipeline.inputFields ?? []);
88
+ }
89
+
35
90
  export type PlaySheetColumnSource =
36
91
  | 'input'
37
92
  | 'datasetColumn'
@@ -73,6 +128,8 @@ export interface PlayStaticColumnProducer {
73
128
  toolId?: string;
74
129
  playId?: string;
75
130
  dependsOnFields?: string[];
131
+ recompute?: boolean;
132
+ recomputeOnError?: boolean;
76
133
  staleAfterSeconds?: number;
77
134
  conditional?: boolean;
78
135
  sourceRange?: PlayStaticSourceRange;
@@ -84,6 +141,8 @@ export interface PlayStaticDatasetColumn {
84
141
  id: string;
85
142
  source: PlaySheetColumnSource;
86
143
  sqlName?: string;
144
+ recompute?: boolean;
145
+ recomputeOnError?: boolean;
87
146
  staleAfterSeconds?: number;
88
147
  producers: PlayStaticColumnProducer[];
89
148
  }
@@ -275,6 +334,8 @@ export interface PlayStaticSourceRange {
275
334
  type PlayStaticSubstepMetadata = {
276
335
  conditional?: boolean;
277
336
  dependsOnFields?: string[];
337
+ recompute?: boolean;
338
+ recomputeOnError?: boolean;
278
339
  staleAfterSeconds?: number;
279
340
  };
280
341
 
@@ -600,13 +661,18 @@ function compileDatasetColumns(
600
661
  );
601
662
  if (!column) continue;
602
663
  const pipelineSubstep =
603
- substep.staleAfterSeconds === undefined
664
+ substep.staleAfterSeconds === undefined &&
665
+ substep.recompute !== true &&
666
+ substep.recomputeOnError !== true
604
667
  ? pipelineSubsteps.find(
605
668
  (candidate) => fieldForColumnProducer(candidate) === field,
606
669
  )
607
670
  : undefined;
608
671
  const producer = columnProducerFromSubstep(
609
- pipelineSubstep && pipelineSubstep.staleAfterSeconds !== undefined
672
+ pipelineSubstep &&
673
+ (pipelineSubstep.staleAfterSeconds !== undefined ||
674
+ pipelineSubstep.recompute === true ||
675
+ pipelineSubstep.recomputeOnError === true)
610
676
  ? pipelineSubstep
611
677
  : substep,
612
678
  field,
@@ -618,6 +684,15 @@ function compileDatasetColumns(
618
684
  ) {
619
685
  column.staleAfterSeconds = producer.staleAfterSeconds;
620
686
  }
687
+ if (column.recompute !== true && producer.recompute === true) {
688
+ column.recompute = true;
689
+ }
690
+ if (
691
+ column.recomputeOnError !== true &&
692
+ producer.recomputeOnError === true
693
+ ) {
694
+ column.recomputeOnError = true;
695
+ }
621
696
  }
622
697
 
623
698
  return [...columnsById.values()];
@@ -667,6 +742,8 @@ function columnProducerFromSubstep(
667
742
  ...(substep.dependsOnFields?.length
668
743
  ? { dependsOnFields: [...substep.dependsOnFields] }
669
744
  : {}),
745
+ ...(substep.recompute === true ? { recompute: true } : {}),
746
+ ...(substep.recomputeOnError === true ? { recomputeOnError: true } : {}),
670
747
  ...(substep.staleAfterSeconds !== undefined
671
748
  ? { staleAfterSeconds: substep.staleAfterSeconds }
672
749
  : {}),
@@ -828,7 +905,14 @@ export function compileSheetContract(pipeline: PlayStaticPipeline): {
828
905
  errors.push('Sheet contract produced an empty column id.');
829
906
  return;
830
907
  }
831
- if (columns.some((existing) => existing.id === column.id)) return;
908
+ const existing = columns.find((candidate) => candidate.id === column.id);
909
+ if (existing) {
910
+ if (existing.source === 'input' && column.source === 'datasetColumn') {
911
+ existing.source = 'datasetColumn';
912
+ existing.field = column.field;
913
+ }
914
+ return;
915
+ }
832
916
  columns.push({
833
917
  ...column,
834
918
  sqlName: sqlSafePlayColumnName(column.id),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.107",
3
+ "version": "0.1.108",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {