deepline 0.3.392 → 0.3.393

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 (145) 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 +418 -44
  12. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +36 -3
  13. package/dist/bundling-sources/shared_libs/play-runtime/durable-invocation-fence.ts +36 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +11 -3
  15. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +1 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +19 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +26 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +158 -10
  19. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +235 -2
  20. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +92 -58
  21. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +7 -1
  22. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +30 -7
  23. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1161 -316
  24. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +28 -15
  25. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +24 -3
  26. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +31 -0
  27. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +37 -4
  28. package/dist/bundling-sources/shared_libs/plays/dataset.ts +172 -79
  29. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +173 -1
  30. package/dist/cli/{auth-6JSA2ZCW.mjs → auth-5PXHA232.mjs} +4 -4
  31. package/dist/cli/auth-P3MVDTXM.js +39 -0
  32. package/dist/cli/{billing-Q5BLQHIU.js → billing-JFB2XLSO.js} +101 -74
  33. package/dist/cli/{billing-MRRO643H.mjs → billing-Z2N7ZAGM.mjs} +32 -5
  34. package/dist/cli/{chunk-CVLM3WMJ.js → chunk-2HAD745V.js} +242 -183
  35. package/dist/cli/{chunk-HGYBEAQP.mjs → chunk-2LGC4EFT.mjs} +3 -3
  36. package/dist/cli/{chunk-A3APGZFP.mjs → chunk-4PT2VA3A.mjs} +2 -2
  37. package/dist/cli/{chunk-6DBOU7UH.js → chunk-4XX6443T.js} +176 -87
  38. package/dist/cli/{chunk-2QLML6G4.mjs → chunk-5KWKZIQR.mjs} +4 -4
  39. package/dist/cli/{chunk-2G7DXFHI.mjs → chunk-5PRVMXKB.mjs} +27 -2
  40. package/dist/cli/{chunk-DSWSEHRX.mjs → chunk-6I73X254.mjs} +1 -1
  41. package/dist/cli/{chunk-ZTACINO5.mjs → chunk-7HR7BGZL.mjs} +13 -3
  42. package/dist/cli/{chunk-BB3FNYFY.js → chunk-AARX5L4B.js} +2 -2
  43. package/dist/cli/{chunk-WMS5U6YB.js → chunk-CDFH2DYL.js} +20 -19
  44. package/dist/cli/{chunk-R3O2CZ4H.mjs → chunk-FQ6VHPW4.mjs} +2 -2
  45. package/dist/cli/{chunk-MU6M3URX.mjs → chunk-HLEESTCH.mjs} +1 -1
  46. package/dist/cli/{chunk-Q6EOHNRH.mjs → chunk-JI2B4RZP.mjs} +119 -6
  47. package/dist/cli/{chunk-2Q3GEFL4.mjs → chunk-KIYU2TGL.mjs} +2 -2
  48. package/dist/cli/{chunk-Y6VOGHF2.mjs → chunk-KZZC33PY.mjs} +0 -181
  49. package/dist/cli/{chunk-UCKJQG25.js → chunk-L2SYLGRP.js} +8 -8
  50. package/dist/cli/{chunk-2GAHHSLB.mjs → chunk-LMMEAMA7.mjs} +3 -2
  51. package/dist/cli/{chunk-YAZCDBXX.mjs → chunk-LZPCDB7U.mjs} +3 -2
  52. package/dist/cli/{chunk-RV6ALZIX.js → chunk-M7Y7JYZX.js} +32 -7
  53. package/dist/cli/{chunk-QGKPTBRR.js → chunk-MS6GUPBB.js} +3 -3
  54. package/dist/cli/{chunk-4SFPVW7A.js → chunk-OXNOAD6Y.js} +26 -26
  55. package/dist/cli/{chunk-JOFPQGXW.js → chunk-PTDECJGP.js} +50 -50
  56. package/dist/cli/{chunk-5O4B6AUN.js → chunk-PWSWAN7Q.js} +25 -2
  57. package/dist/cli/{chunk-YN6YA7ZZ.mjs → chunk-Q26LUSQ2.mjs} +79 -20
  58. package/dist/cli/{chunk-3VN4FTE3.js → chunk-RDXFGPJU.js} +1 -182
  59. package/dist/cli/{chunk-7E66A24V.mjs → chunk-RKMB3N7K.mjs} +25 -2
  60. package/dist/cli/{chunk-JSLPHFP7.js → chunk-RWHWF5ZJ.js} +3 -2
  61. package/dist/cli/{chunk-3FV4GBRK.mjs → chunk-STNYIMIV.mjs} +97 -8
  62. package/dist/cli/chunk-UBKA33KY.mjs +310 -0
  63. package/dist/cli/{chunk-PAIMOGCM.js → chunk-UHNDVD3P.js} +125 -12
  64. package/dist/cli/{chunk-24VEN5PQ.mjs → chunk-UOGZUA24.mjs} +179 -17
  65. package/dist/cli/{chunk-T7OKJ2T6.js → chunk-VIQJIX2P.js} +73 -63
  66. package/dist/cli/{chunk-RIV2GS5E.js → chunk-VIWE6WRK.js} +627 -465
  67. package/dist/cli/{chunk-EY7RFC6A.js → chunk-W4WOMXR3.js} +15 -15
  68. package/dist/cli/chunk-XDVT3GOM.js +310 -0
  69. package/dist/cli/{chunk-2FQG2HU4.js → chunk-Z3OOZM5L.js} +8 -8
  70. package/dist/cli/{csv-E4QGUIGI.mjs → csv-KJWZHKCH.mjs} +3 -3
  71. package/dist/cli/{csv-26U5T24Z.js → csv-P3Z3YE7H.js} +12 -12
  72. package/dist/cli/{db-JTRJIAQW.mjs → db-NCM4CQIS.mjs} +6 -5
  73. package/dist/cli/{db-QHBFLMVL.js → db-NUZGJNQC.js} +31 -30
  74. package/dist/cli/{describe-E3DE3ZK3.mjs → describe-WT6MM2GO.mjs} +17 -16
  75. package/dist/cli/{describe-FDV2V74F.js → describe-YGSEN5WC.js} +24 -23
  76. package/dist/cli/{executions-VZYOGVQY.js → executions-PCOLHN7Q.js} +8 -7
  77. package/dist/cli/{executions-B3GTNPRE.mjs → executions-XS2IPJ4W.mjs} +6 -5
  78. package/dist/cli/{feedback-CR55FJA2.js → feedback-4MSH7TFU.js} +7 -7
  79. package/dist/cli/{feedback-4CEBR3OV.mjs → feedback-CCLTS5SV.mjs} +3 -3
  80. package/dist/cli/{git-NM2PHHZ4.mjs → git-K2DHPMPC.mjs} +4 -4
  81. package/dist/cli/git-RNM45FX4.js +23 -0
  82. package/dist/cli/index.js +96 -92
  83. package/dist/cli/index.mjs +46 -42
  84. package/dist/cli/{legacy-noops-A7MSW5ND.mjs → legacy-noops-B5S4AR3P.mjs} +4 -4
  85. package/dist/cli/{legacy-noops-ZUA4YFIH.js → legacy-noops-GPNOPFXV.js} +7 -7
  86. package/dist/cli/{monitors-NVPRQYP3.mjs → monitors-JLMAIPRG.mjs} +6 -5
  87. package/dist/cli/{monitors-ZE3TLZ4H.js → monitors-ZAWD7NSN.js} +87 -86
  88. package/dist/cli/{org-U7A6DV5D.js → org-P2KN4CES.js} +66 -65
  89. package/dist/cli/{org-KUBILNQV.mjs → org-T3JTHP4G.mjs} +6 -5
  90. package/dist/cli/{play-HRDXQ4QE.mjs → play-NK6BLRGL.mjs} +11 -10
  91. package/dist/cli/play-PDXR6JFB.js +158 -0
  92. package/dist/cli/{providers-OHIWI453.mjs → providers-3NQRA7IT.mjs} +6 -5
  93. package/dist/cli/{providers-VD6YTVQZ.js → providers-5QRZN3DT.js} +9 -8
  94. package/dist/cli/{quickstart-UBV2FONF.mjs → quickstart-PIA2JGPV.mjs} +3 -3
  95. package/dist/cli/{quickstart-ETTCWPA4.js → quickstart-R7NUZGNW.js} +5 -5
  96. package/dist/cli/run-identity-G257GQED.js +25 -0
  97. package/dist/cli/run-identity-MRNCEGV2.mjs +25 -0
  98. package/dist/cli/{search-F44IZRT7.mjs → search-AIDIIGLY.mjs} +7 -6
  99. package/dist/cli/search-QRWL7MFM.js +27 -0
  100. package/dist/cli/{secrets-CAEMRORF.js → secrets-BT3PGMWN.js} +15 -14
  101. package/dist/cli/{secrets-R6XOJZKU.mjs → secrets-OQNHL45O.mjs} +6 -5
  102. package/dist/cli/{sessions-6GC7OFTT.js → sessions-EYXWOSWK.js} +5 -5
  103. package/dist/cli/{sessions-RLN6UQS3.mjs → sessions-SF7WJPJP.mjs} +4 -4
  104. package/dist/cli/{settings-DPNVUAV2.js → settings-BA3GHBOD.js} +76 -75
  105. package/dist/cli/{settings-KIPAMRPP.mjs → settings-Y57JTFDD.mjs} +9 -8
  106. package/dist/cli/{setup-TU2F7PU5.mjs → setup-7GR5GBHA.mjs} +6 -6
  107. package/dist/cli/{setup-DTL3L5SG.js → setup-RERXYC66.js} +41 -41
  108. package/dist/cli/{skills-UV5MYJ46.mjs → skills-SJWIQAV5.mjs} +4 -4
  109. package/dist/cli/skills-V7EY75HB.js +28 -0
  110. package/dist/cli/tools-H5HLBCHH.js +41 -0
  111. package/dist/cli/{tools-QRRNDCQ7.mjs → tools-ZF3JXPNL.mjs} +10 -9
  112. package/dist/cli/update-472HA3LT.js +28 -0
  113. package/dist/cli/{update-4426TF35.mjs → update-FFFYQAOJ.mjs} +6 -6
  114. package/dist/cli/{workflow-HNLOVISB.js → workflow-SZJVQH4T.js} +17 -16
  115. package/dist/cli/{workflow-N4U5VLZ3.mjs → workflow-XVFEEGAS.mjs} +7 -6
  116. package/dist/{client-e6Qh5ZIg.d.mts → client-DU5dtHDy.d.mts} +153 -4
  117. package/dist/{client-Uxuy-xQ2.d.ts → client-mCE88hC4.d.ts} +153 -4
  118. package/dist/{compiler-manifest-CY2v8ab0.d.ts → compiler-manifest-DE4RdAiY.d.mts} +61 -1
  119. package/dist/{compiler-manifest-CY2v8ab0.d.mts → compiler-manifest-DE4RdAiY.d.ts} +61 -1
  120. package/dist/{index-B9fBZH1g.d.mts → index-BI6nWgaV.d.mts} +1 -1
  121. package/dist/{index-BZ6uzZJl.d.ts → index-Bi-f7HFT.d.ts} +1 -1
  122. package/dist/index.d.mts +5 -5
  123. package/dist/index.d.ts +5 -5
  124. package/dist/index.js +120 -29
  125. package/dist/index.mjs +111 -20
  126. package/dist/install-integrity.json +91 -86
  127. package/dist/plays/bundle-play-file.d.mts +3 -3
  128. package/dist/plays/bundle-play-file.d.ts +3 -3
  129. package/dist/plays/bundle-play-file.mjs +2 -2
  130. package/dist/plays/run-request.d.mts +2 -2
  131. package/dist/plays/run-request.d.ts +2 -2
  132. package/dist/plays/run-request.js +5 -2
  133. package/dist/plays/run-request.mjs +4 -1
  134. package/dist/release.d.mts +4 -1
  135. package/dist/release.d.ts +4 -1
  136. package/dist/release.js +2 -2
  137. package/dist/release.mjs +1 -1
  138. package/package.json +1 -1
  139. package/dist/cli/auth-67TAELQL.js +0 -39
  140. package/dist/cli/git-I5JTK6YU.js +0 -23
  141. package/dist/cli/play-A2V7YDU7.js +0 -157
  142. package/dist/cli/search-WWXMBVPH.js +0 -26
  143. package/dist/cli/skills-SWOSEZLB.js +0 -28
  144. package/dist/cli/tools-IL2YCDQ6.js +0 -40
  145. 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,
@@ -710,7 +710,18 @@ function toV2RawToolOutputPathPreservingProviderData(path: string): string {
710
710
  }
711
711
 
712
712
  function candidateResultPaths(path: string): string[] {
713
+ // `result.data` is the public envelope. A following `data` belongs to the
714
+ // provider and must survive (e.g. QuickEnrich's result.data.data rows).
715
+ const normalized = String(path || '')
716
+ .trim()
717
+ .replace(/^\./, '');
718
+ const providerPath = normalized.startsWith('result.data.')
719
+ ? `toolResponse.raw.${normalized.slice('result.data.'.length)}`
720
+ : normalized === 'result.data'
721
+ ? 'toolResponse.raw'
722
+ : '';
713
723
  const candidates = [
724
+ providerPath,
714
725
  normalizeResultPath(path),
715
726
  toV2RawToolOutputPathPreservingProviderData(path),
716
727
  ];
@@ -802,9 +813,29 @@ function acceptedTargetValue(
802
813
  ) {
803
814
  return undefined;
804
815
  }
816
+ if (target === 'email') return emailTargetValue(value) ?? undefined;
805
817
  return isMeaningfulValue(value) ? value : undefined;
806
818
  }
807
819
 
820
+ function emailTargetValue(value: unknown): string | null {
821
+ if (typeof value === 'string') {
822
+ const email = value.trim();
823
+ return /^[^\s@*]+@[^\s@*]+\.[^\s@*]+$/.test(email) ? email : null;
824
+ }
825
+ if (Array.isArray(value)) {
826
+ for (const entry of value) {
827
+ const email = emailTargetValue(entry);
828
+ if (email) return email;
829
+ }
830
+ } else if (isRecord(value)) {
831
+ for (const key of ['email', 'email_address', 'address', 'value']) {
832
+ const email = emailTargetValue(value[key]);
833
+ if (email) return email;
834
+ }
835
+ }
836
+ return null;
837
+ }
838
+
808
839
  function getFirstMeaningfulValueAtPath(
809
840
  root: unknown,
810
841
  path: string,
@@ -12,12 +12,12 @@ 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
18
  export const PLAY_AUTHORING_INPUT_SCHEMA_SNAPSHOT_EDITION = 3 as const;
19
19
  export const SUPPORTED_PLAY_AUTHORING_CONTRACT_EDITIONS = [
20
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
20
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
21
21
  ] as const;
22
22
 
23
23
  export type PlayAuthoringContractEdition =
@@ -96,6 +96,13 @@ const PLAY_AUTHORING_CONTRACT_CHANGELOG_DETAILS = [
96
96
  newWritesEnd: null,
97
97
  readerRemoval: null,
98
98
  },
99
+ {
100
+ changed:
101
+ 'CSV datasets always use source row identity and process every input occurrence, even when key or rowIdentity: "key" is supplied. Editions 1–10 retain their admitted row identity and completed receipts; non-CSV datasets retain explicit key deduplication.',
102
+ compatibilityOwner: 'Plays Runtime',
103
+ newWritesEnd: null,
104
+ readerRemoval: null,
105
+ },
99
106
  ] as const;
100
107
 
101
108
  /** Authoring types and runtime projection must select the same getter contract. */
@@ -105,6 +112,12 @@ export function playToolListGetterMode(
105
112
  return edition >= 10 ? 'array' : 'dataset';
106
113
  }
107
114
 
115
+ export function playCsvDatasetRowIdentity(
116
+ edition: PlayAuthoringContractEdition,
117
+ ): 'source_row' | 'key' {
118
+ return edition >= 11 ? 'source_row' : 'key';
119
+ }
120
+
108
121
  export const PLAY_AUTHORING_CONTRACT_CHANGELOG =
109
122
  SUPPORTED_PLAY_AUTHORING_CONTRACT_EDITIONS.map((edition, index) => ({
110
123
  edition,
@@ -1133,6 +1146,8 @@ export type PlayAuthoringDatasetDefinitionOptions<InputRow extends object> = {
1133
1146
  export type PlayAuthoringDatasetRunOptions<InputRow extends object> = {
1134
1147
  description?: string;
1135
1148
  key?: PlayAuthoringDatasetRowKey<InputRow>;
1149
+ /** CSV defaults to source row identity. Use key only for intentional deduplication. */
1150
+ rowIdentity?: 'source_row' | 'key';
1136
1151
  onRowError?: 'isolate' | 'fail';
1137
1152
  mode?: 'upsert' | 'net_new';
1138
1153
  /**
@@ -1147,6 +1162,7 @@ export type PlayAuthoringDatasetRunOptions<InputRow extends object> = {
1147
1162
  export const PLAY_AUTHORING_DATASET_RUN_OPTION_FIELDS = [
1148
1163
  'description',
1149
1164
  'key',
1165
+ 'rowIdentity',
1150
1166
  'onRowError',
1151
1167
  'mode',
1152
1168
  'undrawnColumns',
@@ -2517,6 +2533,23 @@ export const PLAY_AUTHORING_FIELD_REGISTRY = {
2517
2533
  errorMessage:
2518
2534
  'ctx.dataset run key must be a non-empty field, field list, or key function.',
2519
2535
  },
2536
+ 'ctx.dataset.run.rowIdentity': {
2537
+ schema: Type.Union([Type.Literal('source_row'), Type.Literal('key')]),
2538
+ fixtures: {
2539
+ valid: 'source_row',
2540
+ invalid: 'dedupe',
2541
+ absent: undefined,
2542
+ unresolved: { expression: 'rowIdentity' },
2543
+ edition1: 'key',
2544
+ },
2545
+ referenceType: "'source_row' | 'key'",
2546
+ required: false,
2547
+ resolution: 'static-when-present',
2548
+ issueCode: 'play_authoring_dataset_option_invalid',
2549
+ description:
2550
+ 'Select source_row to process every input occurrence. CSV datasets default to source_row in edition 11; earlier editions retain key identity. Select key only to intentionally deduplicate business keys.',
2551
+ errorMessage: 'ctx.dataset run rowIdentity must be "source_row" or "key".',
2552
+ },
2520
2553
  'ctx.dataset.run.onRowError': {
2521
2554
  schema: Type.Union([Type.Literal('isolate'), Type.Literal('fail')]),
2522
2555
  fixtures: {
@@ -3315,7 +3348,7 @@ export const PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
3315
3348
  'export type StepProgramOutput<TProgram> = TProgram extends StepProgram<unknown, infer Output, unknown> ? Output : never;',
3316
3349
  'export type DatasetRowKey<InputRow extends object> = (keyof InputRow & string) | readonly (keyof InputRow & string)[] | ((row: InputRow, index: number) => string | number | readonly unknown[]);',
3317
3350
  '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')} };`,
3351
+ `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
3352
  'export type DatasetBuilder<InputRow extends object, OutputRow extends object> = {',
3320
3353
  ' withColumn<Name extends string, Value>(name: Name, resolver: ColumnResolver<OutputRow, Value>): DatasetBuilder<InputRow, OutputRow & Record<Name, Value>>;',
3321
3354
  ' 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>>;',
@@ -4,6 +4,118 @@ import type { PlayDatasetColumnTypes } from '../play-data-plane/contracts';
4
4
  const PLAY_DATASET_BRAND = Symbol.for('deepline.play.dataset');
5
5
  const NODE_INSPECT_CUSTOM = Symbol.for('nodejs.util.inspect.custom');
6
6
  const residentRowsByDataset = new WeakMap<object, readonly unknown[]>();
7
+ // Source ordinals are execution provenance, never customer columns. Weak
8
+ // references keep streaming sources bounded and transforms preserve ordinals.
9
+ type PlayDatasetSourceRowIdentity = Readonly<{
10
+ datasetId: string;
11
+ rowIndex: number;
12
+ }>;
13
+ const sourceRowIdentityByRow = new WeakMap<
14
+ object,
15
+ PlayDatasetSourceRowIdentity
16
+ >();
17
+ const sourceRowIdentitiesByRows = new WeakMap<
18
+ readonly unknown[],
19
+ readonly (PlayDatasetSourceRowIdentity | undefined)[]
20
+ >();
21
+ type PlayDatasetSourceOccurrence<T> = {
22
+ row: T;
23
+ sourceIdentity?: PlayDatasetSourceRowIdentity;
24
+ };
25
+ const sourceOccurrencesByDataset = new WeakMap<
26
+ object,
27
+ () => AsyncIterable<PlayDatasetSourceOccurrence<unknown>>
28
+ >();
29
+
30
+ export function setPlayDatasetSourceRowIdentity<T>(
31
+ row: T,
32
+ identity: Readonly<{ datasetId: string; rowIndex: number }>,
33
+ ): T {
34
+ if (row && typeof row === 'object') sourceRowIdentityByRow.set(row, identity);
35
+ return row;
36
+ }
37
+
38
+ export function getPlayDatasetSourceRowIdentity(
39
+ row: unknown,
40
+ ): Readonly<{ datasetId: string; rowIndex: number }> | undefined {
41
+ return row && typeof row === 'object'
42
+ ? sourceRowIdentityByRow.get(row)
43
+ : undefined;
44
+ }
45
+
46
+ /** Container provenance distinguishes occurrences even when a mapper returns a shared typed value. */
47
+ export function getPlayDatasetSourceRowIdentityAt(
48
+ rows: readonly unknown[],
49
+ index: number,
50
+ ): PlayDatasetSourceRowIdentity | undefined {
51
+ const identities = sourceRowIdentitiesByRows.get(rows);
52
+ return identities
53
+ ? identities[index]
54
+ : getPlayDatasetSourceRowIdentity(rows[index]);
55
+ }
56
+
57
+ export function copyPlayDatasetSourceRowIdentity<T>(
58
+ source: unknown,
59
+ target: T,
60
+ ): T {
61
+ const identity = getPlayDatasetSourceRowIdentity(source);
62
+ return identity ? setPlayDatasetSourceRowIdentity(target, identity) : target;
63
+ }
64
+
65
+ /** A mapper may return the same object for several physical source records. */
66
+ function copySourceOccurrenceToMappedValue<T>(
67
+ identity: PlayDatasetSourceRowIdentity | undefined,
68
+ mapped: T,
69
+ ): T {
70
+ if (!identity || !mapped || typeof mapped !== 'object') return mapped;
71
+ const prototype = Object.getPrototypeOf(mapped);
72
+ // Plain row values can carry provenance on a distinct execution object.
73
+ // Typed values retain their mapper result and internal slots; their distinct
74
+ // occurrences travel in the iterator/container sidecar instead.
75
+ const occurrence = (
76
+ Array.isArray(mapped)
77
+ ? Object.defineProperties([], Object.getOwnPropertyDescriptors(mapped))
78
+ : prototype === Object.prototype || prototype === null
79
+ ? Object.create(prototype, Object.getOwnPropertyDescriptors(mapped))
80
+ : mapped
81
+ ) as T;
82
+ return setPlayDatasetSourceRowIdentity(occurrence, identity);
83
+ }
84
+
85
+ async function* iterateSourceOccurrences<T>(
86
+ input: PlayDatasetInput<T>,
87
+ ): AsyncIterable<PlayDatasetSourceOccurrence<T>> {
88
+ if (isPlayDataset(input)) {
89
+ const occurrences = sourceOccurrencesByDataset.get(input);
90
+ if (occurrences) {
91
+ yield* occurrences() as AsyncIterable<PlayDatasetSourceOccurrence<T>>;
92
+ return;
93
+ }
94
+ for await (const row of input)
95
+ yield { row, sourceIdentity: getPlayDatasetSourceRowIdentity(row) };
96
+ return;
97
+ }
98
+ if (Array.isArray(input)) {
99
+ for (let index = 0; index < input.length; index += 1) {
100
+ yield {
101
+ row: input[index]!,
102
+ sourceIdentity: getPlayDatasetSourceRowIdentityAt(input, index),
103
+ };
104
+ }
105
+ return;
106
+ }
107
+ if (
108
+ input != null &&
109
+ typeof input === 'object' &&
110
+ Symbol.asyncIterator in input
111
+ ) {
112
+ for await (const row of input as AsyncIterable<T>)
113
+ yield { row, sourceIdentity: getPlayDatasetSourceRowIdentity(row) };
114
+ return;
115
+ }
116
+ for (const row of input as Iterable<T>)
117
+ yield { row, sourceIdentity: getPlayDatasetSourceRowIdentity(row) };
118
+ }
7
119
  const DEFAULT_MATERIALIZE_LIMIT = 10_000;
8
120
  export const PLAY_DATASET_EXECUTION_PAGE_ROWS = 1_000;
9
121
  export const PLAY_DATASET_EXECUTION_PAGE_BYTES = 64 * 1024 * 1024;
@@ -32,6 +144,14 @@ export type PlayDatasetWorkProgressSummary = {
32
144
  pending: number;
33
145
  failed: number;
34
146
  degraded?: boolean;
147
+ /** Fixed-shape source/output accounting; does not redefine legacy totals. */
148
+ cardinality?: {
149
+ sourceRows: number;
150
+ uniqueInputRows: number;
151
+ duplicateOccurrences: number;
152
+ outputRows: number;
153
+ rowIdentity: 'source_row' | 'key';
154
+ };
35
155
  duplicates?: {
36
156
  exact?: number;
37
157
  semantic?: number;
@@ -658,7 +778,9 @@ function createTransformedPlayDataset<T, U>(
658
778
  options?.sourceLabel ??
659
779
  `${source.sourceLabel ?? source.tableNamespace ?? source.datasetId}.${transform.kind}`;
660
780
 
661
- const iterate = async function* (): AsyncIterable<U> {
781
+ const iterateOccurrences = async function* (): AsyncIterable<
782
+ PlayDatasetSourceOccurrence<U>
783
+ > {
662
784
  if (transform.kind === 'slice') {
663
785
  const bounds = normalizeSliceBounds({
664
786
  start: transform.start,
@@ -666,10 +788,10 @@ function createTransformedPlayDataset<T, U>(
666
788
  count: await source.count(),
667
789
  });
668
790
  let index = 0;
669
- for await (const row of source) {
791
+ for await (const occurrence of iterateSourceOccurrences(source)) {
670
792
  if (index >= bounds.end) break;
671
793
  if (index >= bounds.start) {
672
- yield row as unknown as U;
794
+ yield occurrence as unknown as PlayDatasetSourceOccurrence<U>;
673
795
  }
674
796
  index += 1;
675
797
  }
@@ -678,27 +800,42 @@ function createTransformedPlayDataset<T, U>(
678
800
 
679
801
  let inputIndex = 0;
680
802
  let outputIndex = 0;
681
- for await (const row of source) {
803
+ for await (const { row, sourceIdentity } of iterateSourceOccurrences(
804
+ source,
805
+ )) {
682
806
  if (transform.kind === 'filter') {
683
807
  if (await transform.predicate(row, inputIndex)) {
684
- yield row as unknown as U;
808
+ yield { row: row as unknown as U, sourceIdentity };
685
809
  outputIndex += 1;
686
810
  }
687
811
  } else {
688
- yield await transform.mapper(row, outputIndex);
812
+ yield {
813
+ row: copySourceOccurrenceToMappedValue(
814
+ sourceIdentity,
815
+ await transform.mapper(row, outputIndex),
816
+ ),
817
+ sourceIdentity,
818
+ };
689
819
  outputIndex += 1;
690
820
  }
691
821
  inputIndex += 1;
692
822
  }
693
823
  };
694
824
 
825
+ const iterate = async function* (): AsyncIterable<U> {
826
+ for await (const { row } of iterateOccurrences()) yield row;
827
+ };
828
+
695
829
  const collect = async (limit?: number): Promise<U[]> => {
696
830
  const rows: U[] = [];
831
+ const identities: (PlayDatasetSourceRowIdentity | undefined)[] = [];
832
+ sourceRowIdentitiesByRows.set(rows, identities);
697
833
  const boundedLimit =
698
834
  limit === undefined ? undefined : Math.max(0, Math.floor(limit));
699
835
  if (boundedLimit === 0) return rows;
700
- for await (const row of iterate()) {
836
+ for await (const { row, sourceIdentity } of iterateOccurrences()) {
701
837
  rows.push(row);
838
+ identities.push(sourceIdentity);
702
839
  if (boundedLimit !== undefined && rows.length >= boundedLimit) break;
703
840
  }
704
841
  return rows;
@@ -722,7 +859,7 @@ function createTransformedPlayDataset<T, U>(
722
859
  return total;
723
860
  };
724
861
 
725
- return createDeferredPlayDataset({
862
+ const dataset = createDeferredPlayDataset({
726
863
  datasetKind: source.datasetKind,
727
864
  datasetId: transformDatasetId({
728
865
  source: source as PlayDataset<unknown>,
@@ -747,6 +884,13 @@ function createTransformedPlayDataset<T, U>(
747
884
  }) as AsyncIterable<U>,
748
885
  },
749
886
  });
887
+ sourceOccurrencesByDataset.set(
888
+ dataset,
889
+ iterateOccurrences as () => AsyncIterable<
890
+ PlayDatasetSourceOccurrence<unknown>
891
+ >,
892
+ );
893
+ return dataset;
750
894
  }
751
895
 
752
896
  export function createDeferredPlayDataset<T>(input: {
@@ -922,10 +1066,15 @@ export async function* iteratePlayDatasetInputPages<T>(
922
1066
  : DATASET_UTF8_ENCODER.encode(serialized).byteLength;
923
1067
  });
924
1068
  let page: T[] = [];
1069
+ let pageIdentities: (PlayDatasetSourceRowIdentity | undefined)[] = [];
1070
+ sourceRowIdentitiesByRows.set(page, pageIdentities);
925
1071
  let pageBytes = 0;
926
1072
  let offset = 0;
927
1073
  let index = 0;
928
- const pushRow = (row: T): Array<{ rows: T[]; offset: number }> => {
1074
+ const pushRow = (
1075
+ row: T,
1076
+ sourceIdentity: PlayDatasetSourceRowIdentity | undefined,
1077
+ ): Array<{ rows: T[]; offset: number }> => {
929
1078
  options?.onRow?.(row, index);
930
1079
  index += 1;
931
1080
  const rowBytes = Math.max(0, Math.ceil(estimateRowBytes(row)));
@@ -934,14 +1083,19 @@ export async function* iteratePlayDatasetInputPages<T>(
934
1083
  ready.push({ rows: page, offset });
935
1084
  offset += page.length;
936
1085
  page = [];
1086
+ pageIdentities = [];
1087
+ sourceRowIdentitiesByRows.set(page, pageIdentities);
937
1088
  pageBytes = 0;
938
1089
  }
939
1090
  page.push(row);
1091
+ pageIdentities.push(sourceIdentity);
940
1092
  pageBytes += rowBytes;
941
1093
  if (page.length >= pageSize || pageBytes >= maxPageBytes) {
942
1094
  ready.push({ rows: page, offset });
943
1095
  offset += page.length;
944
1096
  page = [];
1097
+ pageIdentities = [];
1098
+ sourceRowIdentitiesByRows.set(page, pageIdentities);
945
1099
  pageBytes = 0;
946
1100
  }
947
1101
  return ready;
@@ -952,48 +1106,15 @@ export async function* iteratePlayDatasetInputPages<T>(
952
1106
  const finalPage = page;
953
1107
  const finalOffset = offset;
954
1108
  page = [];
1109
+ pageIdentities = [];
1110
+ sourceRowIdentitiesByRows.set(page, pageIdentities);
955
1111
  pageBytes = 0;
956
1112
  offset += finalPage.length;
957
1113
  return { rows: finalPage, offset: finalOffset };
958
1114
  };
959
1115
 
960
- const emitRow = async function* (row: T) {
961
- for (const ready of pushRow(row)) yield ready;
962
- };
963
-
964
- if (isPlayDataset(input)) {
965
- for await (const row of input) {
966
- yield* emitRow(row);
967
- }
968
- const finalPage = flush();
969
- if (finalPage) yield finalPage;
970
- return;
971
- }
972
-
973
- if (Array.isArray(input)) {
974
- for (const row of input) {
975
- yield* emitRow(row);
976
- }
977
- const finalPage = flush();
978
- if (finalPage) yield finalPage;
979
- return;
980
- }
981
-
982
- if (
983
- input != null &&
984
- typeof input === 'object' &&
985
- Symbol.asyncIterator in input
986
- ) {
987
- for await (const row of input as AsyncIterable<T>) {
988
- yield* emitRow(row);
989
- }
990
- const finalPage = flush();
991
- if (finalPage) yield finalPage;
992
- return;
993
- }
994
-
995
- for (const row of input as Iterable<T>) {
996
- yield* emitRow(row);
1116
+ for await (const { row, sourceIdentity } of iterateSourceOccurrences(input)) {
1117
+ for (const ready of pushRow(row, sourceIdentity)) yield ready;
997
1118
  }
998
1119
  const finalPage = flush();
999
1120
  if (finalPage) yield finalPage;
@@ -1003,41 +1124,13 @@ export async function materializePlayDatasetInput<T>(
1003
1124
  input: PlayDatasetInput<T>,
1004
1125
  options?: MaterializePlayDatasetInputOptions<T>,
1005
1126
  ): Promise<T[]> {
1006
- const pushRow = (rows: T[], row: T): void => {
1127
+ const rows: T[] = [];
1128
+ const identities: (PlayDatasetSourceRowIdentity | undefined)[] = [];
1129
+ sourceRowIdentitiesByRows.set(rows, identities);
1130
+ for await (const { row, sourceIdentity } of iterateSourceOccurrences(input)) {
1007
1131
  options?.onRow?.(row, rows.length);
1008
1132
  rows.push(row);
1009
- };
1010
-
1011
- if (isPlayDataset(input)) {
1012
- const rows: T[] = [];
1013
- for await (const row of input) {
1014
- pushRow(rows, row);
1015
- }
1016
- return rows;
1017
- }
1018
-
1019
- if (Array.isArray(input)) {
1020
- const rows: T[] = [];
1021
- for (const row of input) {
1022
- pushRow(rows, row);
1023
- }
1024
- return rows;
1025
- }
1026
-
1027
- const rows: T[] = [];
1028
- if (
1029
- input != null &&
1030
- typeof input === 'object' &&
1031
- Symbol.asyncIterator in input
1032
- ) {
1033
- for await (const row of input as AsyncIterable<T>) {
1034
- pushRow(rows, row);
1035
- }
1036
- return rows;
1037
- }
1038
-
1039
- for (const row of input as Iterable<T>) {
1040
- pushRow(rows, row);
1133
+ identities.push(sourceIdentity);
1041
1134
  }
1042
1135
  return rows;
1043
1136
  }