deepline 0.3.392 → 0.3.394

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +156 -24
  2. package/dist/bundling-sources/sdk/src/compat.ts +9 -0
  3. package/dist/bundling-sources/sdk/src/http.ts +5 -1
  4. package/dist/bundling-sources/sdk/src/index.ts +5 -0
  5. package/dist/bundling-sources/sdk/src/plays/run-request.ts +182 -6
  6. package/dist/bundling-sources/sdk/src/release.ts +4 -1
  7. package/dist/bundling-sources/sdk/src/types.ts +13 -0
  8. package/dist/bundling-sources/shared_libs/integration-contracts/request-warnings.ts +64 -0
  9. package/dist/bundling-sources/shared_libs/play-data-plane/runtime-sheet-storage-contract.ts +315 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +41 -8
  11. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +895 -54
  12. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +41 -4
  13. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-policy.ts +28 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/durable-invocation-fence.ts +36 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/freshness-receipt-execution.ts +251 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +11 -3
  17. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +1 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +30 -1
  19. package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +26 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +168 -10
  21. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +252 -10
  22. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +92 -58
  23. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +7 -1
  24. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +30 -7
  25. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1161 -316
  26. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +28 -15
  27. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +24 -3
  28. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +32 -0
  29. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +103 -23
  30. package/dist/bundling-sources/shared_libs/plays/core.ts +1 -1
  31. package/dist/bundling-sources/shared_libs/plays/dataset.ts +172 -79
  32. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +173 -1
  33. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +18 -0
  34. package/dist/cli/auth-IHKISLJQ.js +39 -0
  35. package/dist/cli/{auth-6JSA2ZCW.mjs → auth-OSXDAVCF.mjs} +4 -4
  36. package/dist/cli/{billing-Q5BLQHIU.js → billing-EKLYOXUY.js} +101 -74
  37. package/dist/cli/{billing-MRRO643H.mjs → billing-ZNNKX3UR.mjs} +32 -5
  38. package/dist/cli/{chunk-4SFPVW7A.js → chunk-2MDDA7A4.js} +26 -26
  39. package/dist/cli/{chunk-JOFPQGXW.js → chunk-354JCXA2.js} +50 -50
  40. package/dist/cli/{chunk-7E66A24V.mjs → chunk-3HU47EGZ.mjs} +71 -9
  41. package/dist/cli/{chunk-DSWSEHRX.mjs → chunk-3HWALOL2.mjs} +1 -1
  42. package/dist/cli/{chunk-T7OKJ2T6.js → chunk-4P27AXEA.js} +73 -63
  43. package/dist/cli/{chunk-24VEN5PQ.mjs → chunk-5BB2RGAX.mjs} +190 -18
  44. package/dist/cli/{chunk-JSLPHFP7.js → chunk-6YAUMG27.js} +3 -2
  45. package/dist/cli/{chunk-MU6M3URX.mjs → chunk-BP33SIZF.mjs} +1 -1
  46. package/dist/cli/{chunk-WMS5U6YB.js → chunk-CS6RAIVY.js} +20 -19
  47. package/dist/cli/{chunk-CVLM3WMJ.js → chunk-E23BQ5SC.js} +242 -183
  48. package/dist/cli/{chunk-2GAHHSLB.mjs → chunk-FJGW5GXU.mjs} +3 -2
  49. package/dist/cli/{chunk-YN6YA7ZZ.mjs → chunk-JHIJIHFB.mjs} +79 -20
  50. package/dist/cli/{chunk-Q6EOHNRH.mjs → chunk-JI2B4RZP.mjs} +119 -6
  51. package/dist/cli/{chunk-5O4B6AUN.js → chunk-KB4SDX5S.js} +71 -9
  52. package/dist/cli/{chunk-RV6ALZIX.js → chunk-KGNTKMRG.js} +32 -7
  53. package/dist/cli/{chunk-6DBOU7UH.js → chunk-KI635JN4.js} +176 -87
  54. package/dist/cli/{chunk-2FQG2HU4.js → chunk-KIUMEOM5.js} +8 -8
  55. package/dist/cli/{chunk-Y6VOGHF2.mjs → chunk-KZZC33PY.mjs} +0 -181
  56. package/dist/cli/{chunk-2Q3GEFL4.mjs → chunk-M57J2SK4.mjs} +2 -2
  57. package/dist/cli/{chunk-R3O2CZ4H.mjs → chunk-MFB2VKBO.mjs} +2 -2
  58. package/dist/cli/{chunk-QGKPTBRR.js → chunk-MH73HMPV.js} +3 -3
  59. package/dist/cli/{chunk-BB3FNYFY.js → chunk-MLPU5SMZ.js} +2 -2
  60. package/dist/cli/{chunk-2G7DXFHI.mjs → chunk-NR3DFKNF.mjs} +27 -2
  61. package/dist/cli/{chunk-EY7RFC6A.js → chunk-QIQBYKLB.js} +15 -15
  62. package/dist/cli/{chunk-RIV2GS5E.js → chunk-RBJYWRCF.js} +638 -466
  63. package/dist/cli/{chunk-3VN4FTE3.js → chunk-RDXFGPJU.js} +1 -182
  64. package/dist/cli/{chunk-YAZCDBXX.mjs → chunk-TJI2EN6K.mjs} +3 -2
  65. package/dist/cli/chunk-UBKA33KY.mjs +310 -0
  66. package/dist/cli/{chunk-PAIMOGCM.js → chunk-UHNDVD3P.js} +125 -12
  67. package/dist/cli/{chunk-4TCT77UF.js → chunk-URWF7NGQ.js} +6 -1
  68. package/dist/cli/{chunk-2QLML6G4.mjs → chunk-UXFSWG3Q.mjs} +4 -4
  69. package/dist/cli/{chunk-UCKJQG25.js → chunk-VK6FYZLP.js} +8 -8
  70. package/dist/cli/{chunk-ZTACINO5.mjs → chunk-X63FDZJZ.mjs} +13 -3
  71. package/dist/cli/chunk-XDVT3GOM.js +310 -0
  72. package/dist/cli/{chunk-A3APGZFP.mjs → chunk-XRQ2AQL7.mjs} +2 -2
  73. package/dist/cli/{chunk-3FV4GBRK.mjs → chunk-XXUDVEXK.mjs} +97 -8
  74. package/dist/cli/{chunk-PQSA443V.mjs → chunk-ZI2ZNDJK.mjs} +6 -1
  75. package/dist/cli/{chunk-HGYBEAQP.mjs → chunk-ZYBGA4AR.mjs} +3 -3
  76. package/dist/cli/command-docs.js +2 -2
  77. package/dist/cli/command-docs.mjs +1 -1
  78. package/dist/cli/{csv-E4QGUIGI.mjs → csv-5AXRB35H.mjs} +3 -3
  79. package/dist/cli/{csv-26U5T24Z.js → csv-LU7O75WE.js} +12 -12
  80. package/dist/cli/{db-JTRJIAQW.mjs → db-7XP5UWEW.mjs} +6 -5
  81. package/dist/cli/{db-QHBFLMVL.js → db-F7YTFKVM.js} +31 -30
  82. package/dist/cli/{describe-FDV2V74F.js → describe-FRH32RDV.js} +24 -23
  83. package/dist/cli/{describe-E3DE3ZK3.mjs → describe-K4ES5LIL.mjs} +17 -16
  84. package/dist/cli/{executions-B3GTNPRE.mjs → executions-IEHEHJUO.mjs} +6 -5
  85. package/dist/cli/{executions-VZYOGVQY.js → executions-RZV5XPT2.js} +8 -7
  86. package/dist/cli/{feedback-CR55FJA2.js → feedback-3P65QTX3.js} +7 -7
  87. package/dist/cli/{feedback-4CEBR3OV.mjs → feedback-FPUVZBAN.mjs} +3 -3
  88. package/dist/cli/{git-NM2PHHZ4.mjs → git-GLVVP7TY.mjs} +4 -4
  89. package/dist/cli/git-MFFAWVVX.js +23 -0
  90. package/dist/cli/index.js +100 -96
  91. package/dist/cli/index.mjs +47 -43
  92. package/dist/cli/{legacy-noops-A7MSW5ND.mjs → legacy-noops-3MOI6NBM.mjs} +4 -4
  93. package/dist/cli/{legacy-noops-ZUA4YFIH.js → legacy-noops-XKSGZR7Q.js} +7 -7
  94. package/dist/cli/{monitors-NVPRQYP3.mjs → monitors-FTBGNEFG.mjs} +6 -5
  95. package/dist/cli/{monitors-ZE3TLZ4H.js → monitors-NZ2DYNST.js} +87 -86
  96. package/dist/cli/{org-U7A6DV5D.js → org-MCKDQXIV.js} +66 -65
  97. package/dist/cli/{org-KUBILNQV.mjs → org-V2J5RBCN.mjs} +6 -5
  98. package/dist/cli/{play-HRDXQ4QE.mjs → play-AJFGME3B.mjs} +11 -10
  99. package/dist/cli/play-WLSN2EO2.js +158 -0
  100. package/dist/cli/{providers-VD6YTVQZ.js → providers-2JEZ2264.js} +9 -8
  101. package/dist/cli/{providers-OHIWI453.mjs → providers-I6RQJZFZ.mjs} +6 -5
  102. package/dist/cli/{quickstart-UBV2FONF.mjs → quickstart-BIDQDMO4.mjs} +3 -3
  103. package/dist/cli/{quickstart-ETTCWPA4.js → quickstart-GJ2RPDU3.js} +5 -5
  104. package/dist/cli/run-identity-G257GQED.js +25 -0
  105. package/dist/cli/run-identity-MRNCEGV2.mjs +25 -0
  106. package/dist/cli/search-5VN4YD4I.js +27 -0
  107. package/dist/cli/{search-F44IZRT7.mjs → search-ELRXFWG5.mjs} +7 -6
  108. package/dist/cli/{secrets-CAEMRORF.js → secrets-53IT6VXD.js} +15 -14
  109. package/dist/cli/{secrets-R6XOJZKU.mjs → secrets-SQXGZXIC.mjs} +6 -5
  110. package/dist/cli/{sessions-RLN6UQS3.mjs → sessions-2X5QF7SL.mjs} +4 -4
  111. package/dist/cli/{sessions-6GC7OFTT.js → sessions-CAV5M77Y.js} +5 -5
  112. package/dist/cli/{settings-DPNVUAV2.js → settings-532WNAZO.js} +76 -75
  113. package/dist/cli/{settings-KIPAMRPP.mjs → settings-7P67VGCJ.mjs} +9 -8
  114. package/dist/cli/{setup-DTL3L5SG.js → setup-6L2VSUWJ.js} +41 -41
  115. package/dist/cli/{setup-TU2F7PU5.mjs → setup-QGGBV2LE.mjs} +6 -6
  116. package/dist/cli/{skills-UV5MYJ46.mjs → skills-3FE62WBG.mjs} +4 -4
  117. package/dist/cli/skills-S6NPZAN7.js +28 -0
  118. package/dist/cli/tools-647RLXQS.js +41 -0
  119. package/dist/cli/{tools-QRRNDCQ7.mjs → tools-DMR25DEF.mjs} +10 -9
  120. package/dist/cli/{update-4426TF35.mjs → update-6RZMHJZP.mjs} +6 -6
  121. package/dist/cli/update-WVTSC3EM.js +28 -0
  122. package/dist/cli/{workflow-N4U5VLZ3.mjs → workflow-L2DQRE6R.mjs} +7 -6
  123. package/dist/cli/{workflow-HNLOVISB.js → workflow-TX4F2PIX.js} +17 -16
  124. package/dist/{client-e6Qh5ZIg.d.mts → client-BktIPdIj.d.mts} +153 -4
  125. package/dist/{client-Uxuy-xQ2.d.ts → client-C3O1Ipf3.d.ts} +153 -4
  126. package/dist/{compiler-manifest-CY2v8ab0.d.ts → compiler-manifest-DLxUY5kI.d.mts} +124 -10
  127. package/dist/{compiler-manifest-CY2v8ab0.d.mts → compiler-manifest-DLxUY5kI.d.ts} +124 -10
  128. package/dist/{index-BZ6uzZJl.d.ts → index-B_GM9E3q.d.ts} +1 -1
  129. package/dist/{index-B9fBZH1g.d.mts → index-SCb0NvDb.d.mts} +1 -1
  130. package/dist/index.d.mts +5 -5
  131. package/dist/index.d.ts +5 -5
  132. package/dist/index.js +120 -29
  133. package/dist/index.mjs +111 -20
  134. package/dist/install-integrity.json +94 -88
  135. package/dist/plays/bundle-play-file.d.mts +3 -3
  136. package/dist/plays/bundle-play-file.d.ts +3 -3
  137. package/dist/plays/bundle-play-file.mjs +2 -2
  138. package/dist/plays/run-request.d.mts +2 -2
  139. package/dist/plays/run-request.d.ts +2 -2
  140. package/dist/plays/run-request.js +5 -2
  141. package/dist/plays/run-request.mjs +4 -1
  142. package/dist/release.d.mts +4 -1
  143. package/dist/release.d.ts +4 -1
  144. package/dist/release.js +2 -2
  145. package/dist/release.mjs +1 -1
  146. package/package.json +1 -1
  147. package/dist/cli/auth-67TAELQL.js +0 -39
  148. package/dist/cli/git-I5JTK6YU.js +0 -23
  149. package/dist/cli/play-A2V7YDU7.js +0 -157
  150. package/dist/cli/search-WWXMBVPH.js +0 -26
  151. package/dist/cli/skills-SWOSEZLB.js +0 -28
  152. package/dist/cli/tools-IL2YCDQ6.js +0 -40
  153. package/dist/cli/update-BERDPEYR.js +0 -28
@@ -2040,7 +2040,7 @@ export async function attachRuntimeEventSearchTriggers(
2040
2040
  await client.query(`
2041
2041
  CREATE OR REPLACE FUNCTION ${schema}.project_origin_log_batch(observation ${schema}.outbox, item jsonb, run_org text, play text, compact_logs boolean, canonical_events boolean, at_time timestamptz, doc jsonb, closed boolean)
2042
2042
  RETURNS text[] LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog AS $function$
2043
- DECLARE fresh text[]; mismatches bigint; expected_ids text[]; existing_identities jsonb;
2043
+ DECLARE fresh text[]; mismatches bigint; expected_keys jsonb; existing_identities jsonb;
2044
2044
  batch_lines jsonb := item->'lines';
2045
2045
  batch_ids jsonb := item->'lineIds';
2046
2046
  batch_contexts jsonb := item->'lineContexts';
@@ -2110,24 +2110,33 @@ export async function attachRuntimeEventSearchTriggers(
2110
2110
  -- Resolve the whole key set once. A per-line LATERAL lookup can choose
2111
2111
  -- a non-leading event_id index scan when scope/org statistics are skewed,
2112
2112
  -- re-reading the same history for every ID even though the PK is unique.
2113
- SELECT array_agg('log-origin:' || observation.run_id || ':' || (id #>> '{}') ORDER BY ordinal)
2114
- INTO expected_ids FROM jsonb_array_elements(batch_ids) WITH ORDINALITY AS ids(id, ordinal);
2113
+ -- Generic plans may scan the scope/org prefix and test ANY(text[]) for
2114
+ -- every row. Use the indexed run prefix and a once-built JSON key set.
2115
+ SELECT jsonb_object_agg('log-origin:' || observation.run_id || ':' || (id #>> '{}'), true)
2116
+ INTO expected_keys FROM jsonb_array_elements(batch_ids) AS ids(id);
2115
2117
  SELECT jsonb_object_agg(event.event_id, jsonb_build_object(
2116
2118
  'runId', event.run_id, 'kind', event.kind, 'digest', event.source_digest))
2117
2119
  INTO existing_identities
2118
2120
  FROM runtime_observability.events event
2119
2121
  WHERE event.runtime_scope = ${quoteLiteral(scope)} AND event.org_id = run_org
2120
- AND event.event_id = ANY(expected_ids);
2122
+ AND event.run_id = observation.run_id AND expected_keys ? event.event_id;
2121
2123
  -- JSON key lookups preserve one proof per identity without a join whose
2122
2124
  -- generic plan can multiply the two variable-size sets. Tombstones keep
2123
2125
  -- their digest/identity: retry never restores an expired body.
2124
- SELECT count(*) INTO mismatches
2125
- FROM jsonb_array_elements(batch_lines) WITH ORDINALITY AS entries(body, ordinal)
2126
- WHERE existing_identities->expected_ids[ordinal]->>'runId' IS DISTINCT FROM observation.run_id
2127
- OR existing_identities->expected_ids[ordinal]->>'kind' IS DISTINCT FROM ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}
2128
- OR existing_identities->expected_ids[ordinal]->>'digest' IS DISTINCT FROM CASE WHEN NULLIF(batch_contexts->(ordinal-1)::int, 'null'::jsonb) IS NULL
2126
+ -- Stream aligned IDs with their lines: positional text[] subscripting
2127
+ -- walks variable-width elements from the start on every lookup.
2128
+ WITH entries AS MATERIALIZED (
2129
+ SELECT body, 'log-origin:' || observation.run_id || ':' || id AS eid,
2130
+ NULLIF(batch_contexts->(ordinal-1)::int, 'null'::jsonb) AS ctx
2131
+ FROM ROWS FROM (jsonb_array_elements(batch_lines), jsonb_array_elements_text(batch_ids))
2132
+ WITH ORDINALITY AS aligned(body, id, ordinal)
2133
+ )
2134
+ SELECT count(*) INTO mismatches FROM entries
2135
+ WHERE existing_identities->eid->>'runId' IS DISTINCT FROM observation.run_id
2136
+ OR existing_identities->eid->>'kind' IS DISTINCT FROM ${RUN_EVENT_KIND_SQL_LITERAL.logAppended}
2137
+ OR existing_identities->eid->>'digest' IS DISTINCT FROM CASE WHEN ctx IS NULL
2129
2138
  THEN encode(sha256(convert_to(body #>> '{}','UTF8')),'hex')
2130
- ELSE encode(sha256(convert_to(jsonb_build_array(body #>> '{}',batch_contexts->(ordinal-1)::int)::text,'UTF8')),'hex') END;
2139
+ ELSE encode(sha256(convert_to(jsonb_build_array(body #>> '{}',ctx)::text,'UTF8')),'hex') END;
2131
2140
  IF mismatches > 0 THEN
2132
2141
  RAISE EXCEPTION 'Origin log identity changed its content or ingress is closed after terminal acceptance';
2133
2142
  END IF;
@@ -2796,8 +2805,9 @@ export async function attachRuntimeEventSearchTriggers(
2796
2805
  RETURNING last_seq INTO assigned_seq;
2797
2806
  -- Build ordinal lookup once. Generic plans estimate a run and unnest
2798
2807
  -- as tiny, then compare every inserted line against the entire ID list
2799
- -- in a nested loop. Fresh IDs came only from this transaction's inserts
2800
- -- for this scope/org/run, so the primary-key set is the complete target.
2808
+ -- in a nested loop or a per-row ANY filter. Fresh IDs came only from
2809
+ -- this transaction's inserts for this scope/org/run, so JSON key
2810
+ -- membership preserves the complete target within the indexed run.
2801
2811
  SELECT jsonb_object_agg(fresh.event_id, fresh.ordinality)
2802
2812
  INTO fresh_positions
2803
2813
  FROM unnest(fresh_event_ids) WITH ORDINALITY AS fresh(event_id, ordinality);
@@ -2805,7 +2815,8 @@ export async function attachRuntimeEventSearchTriggers(
2805
2815
  SET log_seq = assigned_seq - line_count + (fresh_positions->>event.event_id)::bigint
2806
2816
  WHERE event.runtime_scope = ${quoteLiteral(scope)}
2807
2817
  AND event.org_id = run_org
2808
- AND event.event_id = ANY(fresh_event_ids);
2818
+ AND event.run_id = observation.run_id
2819
+ AND fresh_positions ? event.event_id;
2809
2820
  END IF;
2810
2821
  RETURN;
2811
2822
  END
@@ -3730,11 +3741,13 @@ export async function prepareRuntimeEventRetentionIndex(
3730
3741
  };
3731
3742
  let ready = (await inspect()).rows[0];
3732
3743
  if (ready) {
3744
+ // Even IF NOT EXISTS acquires a table lock. A fully proved existing index
3745
+ // needs no shared DDL; invalid or mismatched definitions still fail loudly.
3733
3746
  assertReady(ready);
3734
3747
  } else {
3735
3748
  await client.query(`CREATE INDEX CONCURRENTLY IF NOT EXISTS ${input.name}
3736
- ON runtime_observability.events (ingested_at, event_id)
3737
- WHERE ${input.predicate}`);
3749
+ ON runtime_observability.events (ingested_at, event_id)
3750
+ WHERE ${input.predicate}`);
3738
3751
  ready = (await inspect()).rows[0];
3739
3752
  if (!ready) {
3740
3753
  throw new Error(
@@ -798,10 +798,28 @@ export function classifyToolExecuteHttpFailure(input: {
798
798
  executionFenced,
799
799
  explicitRetryable,
800
800
  });
801
+ // A status GET may fail after a stateful provider operation was accepted.
802
+ // Provider pacing remains useful, but retrying the whole tool would repeat
803
+ // its POST. This explicit recovery proof is stronger than a rejected-call
804
+ // 429; ordinary pacing denials retain their established retry semantics.
805
+ const failureBody = parseJsonObject(input.bodyText);
806
+ const flatPublicDetails = failureBody?.details;
807
+ const acceptedOperationNeedsRecovery =
808
+ explicitRetryable === false &&
809
+ (error instanceof ToolExecutionError
810
+ ? error.origin === 'provider'
811
+ : failureBody?.failure_origin === 'provider') &&
812
+ ((error instanceof ToolExecutionError &&
813
+ error.publicDetails?.operationAccepted === true) ||
814
+ (flatPublicDetails !== null &&
815
+ typeof flatPublicDetails === 'object' &&
816
+ (flatPublicDetails as Record<string, unknown>).operationAccepted ===
817
+ true));
801
818
  const shouldRetry =
802
- providerOwnedRateLimit ||
803
- attestedProviderDeadline ||
804
- (retryDecision.retryable && input.attempt < retryDecision.attemptCap);
819
+ !acceptedOperationNeedsRecovery &&
820
+ (providerOwnedRateLimit ||
821
+ attestedProviderDeadline ||
822
+ (retryDecision.retryable && input.attempt < retryDecision.attemptCap));
805
823
  // In-attempt retry safety and later-run repairability are different
806
824
  // decisions. A non-idempotent 5xx must not be repeated automatically inside
807
825
  // this execution attempt, but an explicit later `plays run` must be able to
@@ -862,6 +880,9 @@ export function classifyToolExecuteHttpFailure(input: {
862
880
  : TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS;
863
881
  return {
864
882
  ...retryDecision,
883
+ ...(acceptedOperationNeedsRecovery
884
+ ? { retryable: false, reason: 'non_retryable_status' as const }
885
+ : {}),
865
886
  error,
866
887
  shouldRetry,
867
888
  isRateLimit: input.status === 429,
@@ -3,6 +3,7 @@ import {
3
3
  RUNTIME_RECEIPT_OUTPUT_MAX_BYTES,
4
4
  } from './output-size-limits';
5
5
  export type {
6
+ CallFreshnessMetadata,
6
7
  SerializedToolExecuteResult,
7
8
  ToolExecuteResult,
8
9
  ToolResultBilling,
@@ -710,7 +711,18 @@ function toV2RawToolOutputPathPreservingProviderData(path: string): string {
710
711
  }
711
712
 
712
713
  function candidateResultPaths(path: string): string[] {
714
+ // `result.data` is the public envelope. A following `data` belongs to the
715
+ // provider and must survive (e.g. QuickEnrich's result.data.data rows).
716
+ const normalized = String(path || '')
717
+ .trim()
718
+ .replace(/^\./, '');
719
+ const providerPath = normalized.startsWith('result.data.')
720
+ ? `toolResponse.raw.${normalized.slice('result.data.'.length)}`
721
+ : normalized === 'result.data'
722
+ ? 'toolResponse.raw'
723
+ : '';
713
724
  const candidates = [
725
+ providerPath,
714
726
  normalizeResultPath(path),
715
727
  toV2RawToolOutputPathPreservingProviderData(path),
716
728
  ];
@@ -802,9 +814,29 @@ function acceptedTargetValue(
802
814
  ) {
803
815
  return undefined;
804
816
  }
817
+ if (target === 'email') return emailTargetValue(value) ?? undefined;
805
818
  return isMeaningfulValue(value) ? value : undefined;
806
819
  }
807
820
 
821
+ function emailTargetValue(value: unknown): string | null {
822
+ if (typeof value === 'string') {
823
+ const email = value.trim();
824
+ return /^[^\s@*]+@[^\s@*]+\.[^\s@*]+$/.test(email) ? email : null;
825
+ }
826
+ if (Array.isArray(value)) {
827
+ for (const entry of value) {
828
+ const email = emailTargetValue(entry);
829
+ if (email) return email;
830
+ }
831
+ } else if (isRecord(value)) {
832
+ for (const key of ['email', 'email_address', 'address', 'value']) {
833
+ const email = emailTargetValue(value[key]);
834
+ if (email) return email;
835
+ }
836
+ }
837
+ return null;
838
+ }
839
+
808
840
  function getFirstMeaningfulValueAtPath(
809
841
  root: unknown,
810
842
  path: string,
@@ -12,12 +12,22 @@ import type { ToolExecutionErrorSchemaVersion } from './tool-execution-error';
12
12
  import type { PlayDataset, PlayDatasetInput, PlayDatasetRow } from './dataset';
13
13
 
14
14
  export const LEGACY_PLAY_AUTHORING_CONTRACT_EDITION = 1 as const;
15
- // New source uses ordinary list values. Historical artifacts keep their pinned
16
- // edition; deploy the edition-10 reader before promoting this writer.
15
+ // Edition 11 readers retain every CSV source row. Promote this writer in the
16
+ // follow-up PR after those readers are deployed everywhere.
17
17
  export const PLAY_AUTHORING_CONTRACT_EDITION = 10 as const;
18
+ export const PLAY_AUTHORING_SLACK_INTERACTIVE_MESSAGE_STRICT_THRESHOLD =
19
+ 10 as const;
20
+ export const EXPLICIT_FRESHNESS_PLAY_AUTHORING_THRESHOLD = 11 as const;
21
+ const EXPLICIT_FRESHNESS_STALE_AFTER_SECONDS_DESCRIPTION = [
22
+ 'Edition 11 requires an explicit choice.',
23
+ '`0` starts a fresh logical call, though a matching operation receipt may recover that same operation after resume;',
24
+ '`null` allows indefinite result reuse;',
25
+ 'a positive integer is the maximum result age in seconds.',
26
+ 'Omitted values retain legacy behavior in older editions.',
27
+ ].join(' ');
18
28
  export const PLAY_AUTHORING_INPUT_SCHEMA_SNAPSHOT_EDITION = 3 as const;
19
29
  export const SUPPORTED_PLAY_AUTHORING_CONTRACT_EDITIONS = [
20
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
30
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
21
31
  ] as const;
22
32
 
23
33
  export type PlayAuthoringContractEdition =
@@ -96,6 +106,13 @@ const PLAY_AUTHORING_CONTRACT_CHANGELOG_DETAILS = [
96
106
  newWritesEnd: null,
97
107
  readerRemoval: null,
98
108
  },
109
+ {
110
+ changed:
111
+ 'Requires explicit freshness and semantic identity for supported durable calls, and gives CSV datasets source-row identity so every input occurrence is processed even when key or rowIdentity: "key" is supplied. Edition 10 retains its list-getter behavior and editions 1–10 retain their admitted CSV identity and completed receipts; non-CSV datasets keep explicit key deduplication.',
112
+ compatibilityOwner: 'Plays Runtime',
113
+ newWritesEnd: null,
114
+ readerRemoval: null,
115
+ },
99
116
  ] as const;
100
117
 
101
118
  /** Authoring types and runtime projection must select the same getter contract. */
@@ -105,6 +122,12 @@ export function playToolListGetterMode(
105
122
  return edition >= 10 ? 'array' : 'dataset';
106
123
  }
107
124
 
125
+ export function playCsvDatasetRowIdentity(
126
+ edition: PlayAuthoringContractEdition,
127
+ ): 'source_row' | 'key' {
128
+ return edition >= 11 ? 'source_row' : 'key';
129
+ }
130
+
108
131
  export const PLAY_AUTHORING_CONTRACT_CHANGELOG =
109
132
  SUPPORTED_PLAY_AUTHORING_CONTRACT_EDITIONS.map((edition, index) => ({
110
133
  edition,
@@ -128,7 +151,7 @@ export const PLAY_AUTHORING_RUNTIME_CAPABILITIES = [
128
151
  capability: 'Web Crypto',
129
152
  surface: '`crypto.subtle`',
130
153
  contract:
131
- 'WebCrypto is available for standards-based signing, verification, encryption, and key import. Keep private material in `ctx.secrets`; `docs-examples/sdk-v2/github-app-jwt.play.ts` signs an RS256 GitHub App JWT and uses `staleAfterSeconds: 0` for its time-varying auth exchange.',
154
+ 'WebCrypto is available for standards-based signing, verification, encryption, and key import. Keep private material in `ctx.secrets`. `docs-examples/sdk-v2/github-app-jwt.play.ts` is an edition-10 compatibility example; edition 11 allows transient fetches only for read-only methods, so do not copy its credential-returning POST into new source.',
132
155
  },
133
156
  {
134
157
  capability: 'Workspace secrets',
@@ -140,7 +163,7 @@ export const PLAY_AUTHORING_RUNTIME_CAPABILITIES = [
140
163
  capability: 'Freshness',
141
164
  surface: '`staleAfterSeconds` on durable calls',
142
165
  contract:
143
- 'This is call-receipt freshness: omitted/null reuses forever, zero always executes, and a positive integer is a TTL in seconds. It never changes dataset or request identity.',
166
+ 'New source in edition 11 must choose freshness explicitly. Zero executes a new logical call, a positive integer is a maximum result age in seconds, and null allows indefinite reuse. A completed operation receipt separately recovers the same intended operation after resume.',
144
167
  },
145
168
  ] as const;
146
169
 
@@ -1027,7 +1050,7 @@ export type PlayAuthoringStepOptions<Row, Value = unknown> = {
1027
1050
  /** Legacy error-recompute flag accepted for older authored Plays. */
1028
1051
  readonly recomputeOnError?: boolean;
1029
1052
  /** Legacy cell staleness metadata accepted for older authored Plays. */
1030
- readonly staleAfterSeconds?: number;
1053
+ readonly staleAfterSeconds?: DurableCallStaleAfterSeconds;
1031
1054
  };
1032
1055
 
1033
1056
  /** Explicitly mark a step program as a provider fallback waterfall. */
@@ -1091,7 +1114,7 @@ export type PlayAuthoringStepProgramStep<TContext> = {
1091
1114
  readonly name: string;
1092
1115
  readonly recompute?: boolean;
1093
1116
  readonly recomputeOnError?: boolean;
1094
- readonly staleAfterSeconds?: number;
1117
+ readonly staleAfterSeconds?: DurableCallStaleAfterSeconds;
1095
1118
  readonly resolver:
1096
1119
  | PlayAuthoringStepResolver<Record<string, unknown>, unknown, TContext>
1097
1120
  | PlayAuthoringConditionalStepResolver<
@@ -1133,6 +1156,8 @@ export type PlayAuthoringDatasetDefinitionOptions<InputRow extends object> = {
1133
1156
  export type PlayAuthoringDatasetRunOptions<InputRow extends object> = {
1134
1157
  description?: string;
1135
1158
  key?: PlayAuthoringDatasetRowKey<InputRow>;
1159
+ /** Edition 11 CSVs always use source-row identity; key-based deduplication remains for non-CSV inputs and earlier editions. */
1160
+ rowIdentity?: 'source_row' | 'key';
1136
1161
  onRowError?: 'isolate' | 'fail';
1137
1162
  mode?: 'upsert' | 'net_new';
1138
1163
  /**
@@ -1147,6 +1172,7 @@ export type PlayAuthoringDatasetRunOptions<InputRow extends object> = {
1147
1172
  export const PLAY_AUTHORING_DATASET_RUN_OPTION_FIELDS = [
1148
1173
  'description',
1149
1174
  'key',
1175
+ 'rowIdentity',
1150
1176
  'onRowError',
1151
1177
  'mode',
1152
1178
  'undrawnColumns',
@@ -1343,8 +1369,10 @@ export type PlayAuthoringRuntimeStepOptions = {
1343
1369
  /**
1344
1370
  * Durable freshness for one `ctx.fetch` call. The key identifies the request;
1345
1371
  * `staleAfterSeconds` controls when that completed request may be reused across
1346
- * Play runs. Omit it (or pass `null`) to reuse indefinitely, pass `0` to
1347
- * execute on every new run, or pass a positive number of seconds for a TTL.
1372
+ * Play runs. Edition 11 requires an explicit choice: `null` reuses indefinitely,
1373
+ * `0` executes on every new run, and a positive whole number sets the maximum
1374
+ * result age. Retained results are supported only for read methods (GET, HEAD,
1375
+ * OPTIONS); mutating methods require `0`. Transient calls do not retain results.
1348
1376
  *
1349
1377
  * Use this for polling or periodically refreshing one external resource. It
1350
1378
  * does not change dataset row identity.
@@ -1352,7 +1380,7 @@ export type PlayAuthoringRuntimeStepOptions = {
1352
1380
  */
1353
1381
  export type PlayAuthoringFetchOptions = {
1354
1382
  staleAfterSeconds?: DurableCallStaleAfterSeconds;
1355
- /** Run through Deepline's guarded outbound path without retaining a response receipt or checkpoint. Use this for short-lived credential exchanges or other response data that should stay in the running Play only. */
1383
+ /** Run through Deepline's guarded outbound path without retaining a response receipt or checkpoint. Edition 11 permits transient fetches only for GET, HEAD, and OPTIONS. */
1356
1384
  transient?: boolean;
1357
1385
  };
1358
1386
 
@@ -1430,6 +1458,8 @@ export class CtxFetchHttpError extends Error {
1430
1458
  }
1431
1459
 
1432
1460
  export type PlayAuthoringCustomerDbQueryOptions = {
1461
+ id?: string;
1462
+ staleAfterSeconds?: 0;
1433
1463
  maxRows?: number;
1434
1464
  timeoutMs?: number;
1435
1465
  };
@@ -1555,7 +1585,10 @@ export interface PlayAuthoringRuntimeContext<
1555
1585
  key: string,
1556
1586
  toolId: string,
1557
1587
  input: Record<string, unknown>,
1558
- options?: { description?: string },
1588
+ options?: {
1589
+ description?: string;
1590
+ staleAfterSeconds?: DurableCallStaleAfterSeconds;
1591
+ },
1559
1592
  ): Promise<
1560
1593
  TListGetter extends 'array'
1561
1594
  ? BasicToolExecuteResult<TOutput>
@@ -1586,7 +1619,7 @@ export interface PlayAuthoringRuntimeContext<
1586
1619
  ): Promise<T>;
1587
1620
 
1588
1621
  /**
1589
- * Execute a guarded HTTP request. By default, completed responses are durably reused; `staleAfterSeconds` governs only that completed call receipt, never dataset/request identity. Failed requests do not publish completed cache entries. A headers timeout leaves an upstream write's outcome unknown and does not establish whether its body was sent. The transport does not automatically retry that timed-out write, but a later invocation can execute it again. Mutating requests require provider-supported idempotency with the same key retained across retries and replay. Deadlines are platform-controlled; callers may cancel through `init.signal`. Pass `{ transient: true }` for short-lived credential exchanges or other response data that must not be retained in a receipt or checkpoint. Edition 5+ throws `CtxFetchHttpError` for non-2xx; catch it only when the Play intentionally recovers, otherwise let it fail the Play. Editions 1–4 retain their previous response projections.
1622
+ * Execute a guarded HTTP request. Edition 11 requires an explicit freshness choice; only GET, HEAD, and OPTIONS can retain completed responses. Mutating methods must use `staleAfterSeconds: 0`; the choice governs only the completed call receipt, never dataset/request identity. Failed requests do not publish completed cache entries. A headers timeout leaves an upstream write's outcome unknown and does not establish whether its body was sent. The transport does not automatically retry that timed-out write, but a later invocation can execute it again. Mutating requests require provider-supported idempotency with the same key retained across retries and replay. Deadlines are platform-controlled; callers may cancel through `init.signal`. Pass `{ transient: true }` only on GET, HEAD, and OPTIONS when the response must not be retained in a receipt or checkpoint; edition 11 rejects transient writes. Edition 5+ throws `CtxFetchHttpError` for non-2xx; catch it only when the Play intentionally recovers, otherwise let it fail the Play. Editions 1–4 retain their previous response projections.
1590
1623
  *
1591
1624
  * @sdkReference runtime 170 ctx.fetch(key, url, init)
1592
1625
  */
@@ -2257,8 +2290,7 @@ export const PLAY_AUTHORING_FIELD_REGISTRY = {
2257
2290
  required: false,
2258
2291
  resolution: 'runtime-allowed',
2259
2292
  issueCode: 'play_authoring_durable_policy_invalid',
2260
- description:
2261
- '`0` always executes; `null`/omitted never expires; a positive integer is a TTL in seconds.',
2293
+ description: EXPLICIT_FRESHNESS_STALE_AFTER_SECONDS_DESCRIPTION,
2262
2294
  errorMessage:
2263
2295
  'staleAfterSeconds must be null, 0 (always execute), or a positive whole number of seconds.',
2264
2296
  },
@@ -2517,6 +2549,23 @@ export const PLAY_AUTHORING_FIELD_REGISTRY = {
2517
2549
  errorMessage:
2518
2550
  'ctx.dataset run key must be a non-empty field, field list, or key function.',
2519
2551
  },
2552
+ 'ctx.dataset.run.rowIdentity': {
2553
+ schema: Type.Union([Type.Literal('source_row'), Type.Literal('key')]),
2554
+ fixtures: {
2555
+ valid: 'source_row',
2556
+ invalid: 'dedupe',
2557
+ absent: undefined,
2558
+ unresolved: { expression: 'rowIdentity' },
2559
+ edition1: 'key',
2560
+ },
2561
+ referenceType: "'source_row' | 'key'",
2562
+ required: false,
2563
+ resolution: 'static-when-present',
2564
+ issueCode: 'play_authoring_dataset_option_invalid',
2565
+ description:
2566
+ 'Edition 11 CSV inputs always use source-row occurrence identity, even with rowIdentity: key; key-based deduplication remains available for non-CSV inputs and editions 1–10.',
2567
+ errorMessage: 'ctx.dataset run rowIdentity must be "source_row" or "key".',
2568
+ },
2520
2569
  'ctx.dataset.run.onRowError': {
2521
2570
  schema: Type.Union([Type.Literal('isolate'), Type.Literal('fail')]),
2522
2571
  fixtures: {
@@ -2613,8 +2662,7 @@ export const PLAY_AUTHORING_FIELD_REGISTRY = {
2613
2662
  required: false,
2614
2663
  resolution: 'runtime-dynamic',
2615
2664
  issueCode: 'play_authoring_durable_policy_invalid',
2616
- description:
2617
- 'Checkpoint freshness: null/omitted never expires, 0 always executes.',
2665
+ description: EXPLICIT_FRESHNESS_STALE_AFTER_SECONDS_DESCRIPTION,
2618
2666
  errorMessage:
2619
2667
  'staleAfterSeconds must be null, 0 (always execute), or a positive whole number of seconds.',
2620
2668
  },
@@ -2648,8 +2696,7 @@ export const PLAY_AUTHORING_FIELD_REGISTRY = {
2648
2696
  required: false,
2649
2697
  resolution: 'runtime-dynamic',
2650
2698
  issueCode: 'play_authoring_durable_policy_invalid',
2651
- description:
2652
- 'Fetch freshness: null/omitted never expires, 0 always executes.',
2699
+ description: EXPLICIT_FRESHNESS_STALE_AFTER_SECONDS_DESCRIPTION,
2653
2700
  errorMessage:
2654
2701
  'staleAfterSeconds must be null, 0 (always execute), or a positive whole number of seconds.',
2655
2702
  },
@@ -2903,6 +2950,39 @@ export const PLAY_AUTHORING_FIELD_REGISTRY = {
2903
2950
  errorMessage:
2904
2951
  'ctx.customerDb.query statement must be a non-empty SQL string. Deprecated SqlQuery objects cannot contain parameter values.',
2905
2952
  },
2953
+ 'ctx.customerDb.query.options.id': {
2954
+ schema: Type.String({ pattern: '\\S' }),
2955
+ fixtures: {
2956
+ valid: 'customer-report-read',
2957
+ invalid: '',
2958
+ absent: undefined,
2959
+ unresolved: { expression: 'operationId' },
2960
+ edition1: 'query',
2961
+ },
2962
+ referenceType: 'string',
2963
+ required: true,
2964
+ resolution: 'runtime-dynamic',
2965
+ issueCode: 'play_authoring_tool_request_invalid',
2966
+ description: 'Stable operation identity for Customer DB query recovery.',
2967
+ errorMessage:
2968
+ 'ctx.customerDb.query options.id must be a non-empty stable string.',
2969
+ },
2970
+ 'ctx.customerDb.query.options.staleAfterSeconds': {
2971
+ schema: Type.Literal(0),
2972
+ fixtures: {
2973
+ valid: 0,
2974
+ invalid: 1,
2975
+ absent: undefined,
2976
+ unresolved: { expression: 'ttl' },
2977
+ edition1: 0,
2978
+ },
2979
+ referenceType: 'number | null',
2980
+ required: false,
2981
+ resolution: 'runtime-dynamic',
2982
+ issueCode: 'play_authoring_durable_policy_invalid',
2983
+ description: 'Customer DB queries always execute with freshness 0.',
2984
+ errorMessage: 'Customer DB queries must use staleAfterSeconds: 0.',
2985
+ },
2906
2986
  'ctx.customerDb.query.options.maxRows': {
2907
2987
  schema: Type.Integer({ minimum: 1 }),
2908
2988
  fixtures: {
@@ -3304,18 +3384,18 @@ export const PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
3304
3384
  'export type DatasetColumnRunInput<Row, Value> = { readonly row: Row; readonly ctx: DeeplinePlayRuntimeContext; readonly index: number; readonly previousCell?: PreviousCell<Value> };',
3305
3385
  'export type DatasetColumnDefinition<Row, Value> = { readonly run: (input: DatasetColumnRunInput<Row, Value>) => Value | Promise<Value>; readonly runIf?: (row: Row, index: number) => boolean | Promise<boolean> };',
3306
3386
  "export type ConditionalStepResolver<Row, Value, Else = null> = { readonly kind: 'conditional'; readonly when: (row: Row, index: number) => boolean | Promise<boolean>; readonly run: StepResolver<Row, Value>; readonly elseValue: Else; else<ValueElse>(value: ValueElse): ConditionalStepResolver<Row, Value, ValueElse>; };",
3307
- 'export type StepOptions<Row, Value = unknown> = { readonly runIf?: (row: Row, index: number) => boolean | Promise<boolean>; readonly recompute?: boolean; readonly recomputeOnError?: boolean; readonly staleAfterSeconds?: number };',
3387
+ 'export type StepOptions<Row, Value = unknown> = { readonly runIf?: (row: Row, index: number) => boolean | Promise<boolean>; readonly recompute?: boolean; readonly recomputeOnError?: boolean; readonly staleAfterSeconds?: DurableCallStaleAfterSeconds };',
3308
3388
  'export type StepProgramOptions = { readonly continueOnProviderUnavailable?: boolean };',
3309
3389
  "export type StepProgram<Input, Output, Return = Output> = { readonly kind: 'steps'; readonly steps: readonly PlayStepProgramStep[]; readonly returnResolver?: StepResolver<Output, Return>; readonly continueOnProviderUnavailable?: boolean; readonly __inputType?: (input: Input) => void; step<Name extends string, Value>(name: Name, resolver: StepResolver<Output, Value> | ConditionalStepResolver<Output, Value> | StepProgramResolver<Output, Value>): StepProgram<Input, Output & Record<Name, Value>, Return>; step<Name extends string, Value>(name: Name, resolver: StepResolver<Output, Value> | StepProgramResolver<Output, Value>, options: StepOptions<Output, Value>): StepProgram<Input, Output & Record<Name, Value | null>, Return>; return<Value>(resolver: StepResolver<Output, Value>): StepProgram<Input, Output, Value>; };",
3310
3390
  "export type StepProgramResolver<Input, Return> = { readonly kind: 'steps'; readonly steps: readonly PlayStepProgramStep[]; readonly returnResolver?: StepResolver<never, Return>; readonly __inputType?: (input: Input) => void };",
3311
3391
  "export type RunnableStepProgram<Input, Return> = Pick<StepProgram<Input, never, Return>, 'kind' | 'steps' | 'returnResolver' | '__inputType'>;",
3312
3392
  "export type RunnableColumnStepProgram<Return> = Pick<StepProgramResolver<unknown, Return>, 'kind' | 'steps' | 'returnResolver'>;",
3313
- 'export type PlayStepProgramStep = { readonly name: string; readonly recompute?: boolean; readonly recomputeOnError?: boolean; readonly staleAfterSeconds?: number; readonly resolver: StepResolver<Record<string, unknown>, unknown> | ConditionalStepResolver<Record<string, unknown>, unknown> | StepProgramResolver<Record<string, unknown>, unknown> };',
3393
+ 'export type PlayStepProgramStep = { readonly name: string; readonly recompute?: boolean; readonly recomputeOnError?: boolean; readonly staleAfterSeconds?: DurableCallStaleAfterSeconds; readonly resolver: StepResolver<Record<string, unknown>, unknown> | ConditionalStepResolver<Record<string, unknown>, unknown> | StepProgramResolver<Record<string, unknown>, unknown> };',
3314
3394
  'export type ColumnResolver<Row, Value> = StepResolver<Row, Value> | ConditionalStepResolver<Row, Value> | RunnableColumnStepProgram<Value>;',
3315
3395
  'export type StepProgramOutput<TProgram> = TProgram extends StepProgram<unknown, infer Output, unknown> ? Output : never;',
3316
3396
  'export type DatasetRowKey<InputRow extends object> = (keyof InputRow & string) | readonly (keyof InputRow & string)[] | ((row: InputRow, index: number) => string | number | readonly unknown[]);',
3317
3397
  'export type DatasetDefinitionOptions<InputRow extends object> = { key?: DatasetRowKey<InputRow> };',
3318
- `export type DatasetRunOptions<InputRow extends object> = { description?: ${cloudReferenceType('ctx.dataset.run.description')}; key?: DatasetRowKey<InputRow>; onRowError?: ${cloudReferenceType('ctx.dataset.run.onRowError')}; mode?: ${cloudReferenceType('ctx.dataset.run.mode')}; undrawnColumns?: ${cloudReferenceType('ctx.dataset.run.undrawnColumns')} };`,
3398
+ `export type DatasetRunOptions<InputRow extends object> = { description?: ${cloudReferenceType('ctx.dataset.run.description')}; key?: DatasetRowKey<InputRow>; rowIdentity?: ${cloudReferenceType('ctx.dataset.run.rowIdentity')}; onRowError?: ${cloudReferenceType('ctx.dataset.run.onRowError')}; mode?: ${cloudReferenceType('ctx.dataset.run.mode')}; undrawnColumns?: ${cloudReferenceType('ctx.dataset.run.undrawnColumns')} };`,
3319
3399
  'export type DatasetBuilder<InputRow extends object, OutputRow extends object> = {',
3320
3400
  ' withColumn<Name extends string, Value>(name: Name, resolver: ColumnResolver<OutputRow, Value>): DatasetBuilder<InputRow, OutputRow & Record<Name, Value>>;',
3321
3401
  ' withColumn<Name extends string, Value>(name: Name, definition: DatasetColumnDefinition<OutputRow, Value> & { readonly runIf: (row: OutputRow, index: number) => boolean | Promise<boolean> }): DatasetBuilder<InputRow, OutputRow & Record<Name, Value | null>>;',
@@ -3335,8 +3415,8 @@ export const PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
3335
3415
  ' readonly run: { readonly id: string };',
3336
3416
  ` runSteps<TInput extends Record<string, unknown>, TOutput>(program: RunnableStepProgram<TInput, TOutput>, input: TInput, options?: { description?: ${cloudReferenceType('ctx.runSteps.options.description')} }): Promise<TOutput>;`,
3337
3417
  ' tools: { execute<K extends string>(request: ToolExecutionRequest<K>): Promise<ToolExecutionOutput<K>> };',
3338
- ` 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[]> };`,
3339
- ` tool<K extends string>(key: ${cloudReferenceType('ctx.tool.key')}, toolId: K, input: ${cloudReferenceType('ctx.tool.input')}, options?: { description?: ${cloudReferenceType('ctx.tool.options.description')} }): Promise<ToolExecutionOutput<K>>;`,
3418
+ ` customerDb: { query<TRow extends Record<string, unknown> = Record<string, unknown>>(statement: SqlQuery, options?: { id?: ${cloudReferenceType('ctx.customerDb.query.options.id')}; staleAfterSeconds?: ${cloudReferenceType('ctx.customerDb.query.options.staleAfterSeconds')}; maxRows?: ${cloudReferenceType('ctx.customerDb.query.options.maxRows')}; timeoutMs?: ${cloudReferenceType('ctx.customerDb.query.options.timeoutMs')} }): Promise<TRow[]> };`,
3419
+ ` tool<K extends string>(key: ${cloudReferenceType('ctx.tool.key')}, toolId: K, input: ${cloudReferenceType('ctx.tool.input')}, options?: { description?: ${cloudReferenceType('ctx.tool.options.description')}; staleAfterSeconds?: DurableCallStaleAfterSeconds }): Promise<ToolExecutionOutput<K>>;`,
3340
3420
  ' step<T>(id: string, run: () => T | Promise<T>, options?: RuntimeStepOptions): Promise<T>;',
3341
3421
  " fetch(key: string, url: string | URL, init?: RequestInit & { headers?: HeadersInit & { 'Idempotency-Key'?: string; 'X-Idempotency-Key'?: string }; auth?: SecretAuthInput }, options?: FetchOptions): Promise<PlayFetchResponse>;",
3342
3422
  ' secrets: { get(name: string): SecretPromise; bearer(secret: string | SecretPromise | SecretHandle): SecretAuth; header(header: string, secret: string | SecretPromise | SecretHandle): SecretAuth };',
@@ -270,7 +270,7 @@ class PlayCoreStepProgram<Input, Output, ReturnValue> implements StepProgram<
270
270
  ...(options?.recomputeOnError === true
271
271
  ? { recomputeOnError: true }
272
272
  : {}),
273
- ...(typeof options?.staleAfterSeconds === 'number'
273
+ ...(options?.staleAfterSeconds !== undefined
274
274
  ? { staleAfterSeconds: options.staleAfterSeconds }
275
275
  : {}),
276
276
  resolver: stepResolver as PlayStepProgramStep['resolver'],