deepline 0.3.460 → 0.3.462

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 (297) hide show
  1. package/dist/bundling-sources/sdk/src/client-core.ts +7593 -0
  2. package/dist/bundling-sources/sdk/src/client.ts +8 -7366
  3. package/dist/bundling-sources/sdk/src/http-core.ts +1239 -0
  4. package/dist/bundling-sources/sdk/src/http.ts +6 -1231
  5. package/dist/bundling-sources/sdk/src/index.ts +4 -1
  6. package/dist/bundling-sources/sdk/src/play.ts +5 -5
  7. package/dist/bundling-sources/sdk/src/plays/client-core.ts +437 -0
  8. package/dist/bundling-sources/sdk/src/plays/client.ts +4 -421
  9. package/dist/bundling-sources/sdk/src/plays/input-example.ts +3 -4
  10. package/dist/bundling-sources/sdk/src/plays/play-description.ts +1 -1
  11. package/dist/bundling-sources/sdk/src/plays/run-request.ts +1 -1
  12. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  13. package/dist/bundling-sources/sdk/src/server.ts +43 -0
  14. package/dist/bundling-sources/sdk/src/types.ts +15 -5
  15. package/dist/bundling-sources/shared_libs/integration-contracts/billing-entitlements.ts +15 -0
  16. package/dist/bundling-sources/shared_libs/monitors/billing/state.ts +28 -0
  17. package/dist/bundling-sources/shared_libs/monitors/callback-security.ts +0 -307
  18. package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +3 -0
  19. package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +3 -2
  20. package/dist/bundling-sources/shared_libs/monitors/package.json +2 -0
  21. package/dist/bundling-sources/shared_libs/monitors/provider-callback-verification.ts +34 -0
  22. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +59 -0
  23. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-upstream-identity.ts +94 -1
  24. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-dead-letter.ts +35 -1
  25. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-failure-codes.ts +24 -0
  26. package/dist/bundling-sources/shared_libs/observability/dlq.ts +32 -0
  27. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +57 -3
  28. package/dist/bundling-sources/shared_libs/observability/scheduled-work.ts +1 -0
  29. package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +66 -1
  30. package/dist/bundling-sources/shared_libs/play-data-plane/sheet-column-storage.ts +36 -7
  31. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +250 -11
  32. package/dist/bundling-sources/shared_libs/play-runtime/array-input-store.ts +319 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +991 -359
  34. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +17 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +13 -0
  36. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +110 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +155 -6
  38. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +110 -6
  39. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +127 -13
  40. package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +14 -1
  41. package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +57 -1
  42. package/dist/bundling-sources/shared_libs/play-runtime/free-company-lookup-batching.ts +218 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +42 -5
  44. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-budget-state-backend.ts +6 -1
  45. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +92 -31
  46. package/dist/bundling-sources/shared_libs/play-runtime/governor/block-reserving-budget-state-backend.ts +69 -14
  47. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +93 -23
  48. package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +15 -4
  49. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +22 -18
  50. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +5 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/launch-identity.ts +2 -0
  52. package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +190 -14
  53. package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +131 -0
  54. package/dist/bundling-sources/shared_libs/play-runtime/modal-runtime-config.ts +77 -2
  55. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +271 -2
  56. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +51 -0
  57. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/contracts.ts +2 -0
  58. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/job.ts +27 -1
  59. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +2 -0
  60. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +3 -0
  61. package/dist/bundling-sources/shared_libs/play-runtime/provider-text-for-log.ts +63 -0
  62. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +19 -0
  63. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger-event-transport.ts +112 -16
  64. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +104 -2
  65. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +265 -151
  66. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-gateway-transport.ts +17 -0
  67. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +35 -14
  68. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-sheet-persistence-retry.ts +12 -1
  69. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-control-transport.ts +4 -0
  70. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-labels.ts +36 -0
  71. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +136 -70
  72. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +237 -36
  73. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +148 -4
  74. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +6 -0
  75. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +285 -32
  76. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +251 -56
  77. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +29 -4
  78. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/sandbox-create-outcome.ts +27 -0
  79. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +22 -1
  80. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +1397 -382
  81. package/dist/bundling-sources/shared_libs/play-runtime/runtime-array-input-receipts.ts +920 -0
  82. package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +16 -0
  83. package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +38 -0
  84. package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +6 -0
  85. package/dist/bundling-sources/shared_libs/play-runtime/runtime-incident-drills.ts +26 -1
  86. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +30 -0
  87. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +14 -3
  88. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +31 -4
  89. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-transition.ts +47 -0
  90. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-compute-usage.ts +20 -7
  91. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +12 -0
  92. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/index.ts +9 -0
  93. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +10 -0
  94. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +4 -0
  95. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +1496 -320
  96. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +31 -1
  97. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1389 -129
  98. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +36 -0
  99. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-dispatch-authority.ts +4 -0
  100. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +162 -23
  101. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +499 -115
  102. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +377 -9
  103. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-ledger-reconciliation.ts +117 -8
  104. package/dist/bundling-sources/shared_libs/play-runtime/secret-resolution-retry-policy.ts +25 -0
  105. package/dist/bundling-sources/shared_libs/play-runtime/terminal-runtime-performance.ts +391 -0
  106. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +40 -0
  107. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +6 -0
  108. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/mapped-calls.ts +171 -0
  109. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +16 -1
  110. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +26 -3
  111. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +16 -2
  112. package/dist/bundling-sources/shared_libs/play-runtime/transport-error-diagnostics.ts +28 -0
  113. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-storage-contract.ts +55 -5
  114. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +41 -1
  115. package/dist/bundling-sources/shared_libs/plays/contracts.ts +4 -0
  116. package/dist/bundling-sources/shared_libs/plays/dataset.ts +9 -3
  117. package/dist/bundling-sources/shared_libs/plays/deploy/manifest-contract.ts +2 -0
  118. package/dist/bundling-sources/shared_libs/plays/deploy/manifest.ts +3 -2
  119. package/dist/bundling-sources/shared_libs/plays/deploy/project-serializer.ts +2 -2
  120. package/dist/bundling-sources/shared_libs/plays/run-output-summary.ts +66 -0
  121. package/dist/bundling-sources/shared_libs/plays/script-entry.ts +1 -1
  122. package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +43 -1
  123. package/dist/bundling-sources/shared_libs/product-notifications/content.ts +9 -0
  124. package/dist/bundling-sources/shared_libs/product-notifications/email.ts +91 -13
  125. package/dist/bundling-sources/shared_libs/product-notifications/events.ts +3 -0
  126. package/dist/bundling-sources/shared_libs/product-notifications/slack-delivery.ts +41 -24
  127. package/dist/bundling-sources/shared_libs/security/attested-client-ip.ts +91 -0
  128. package/dist/bundling-sources/shared_libs/security/internal-actor.ts +0 -11
  129. package/dist/cli/{auth-HKA6ZVHQ.mjs → auth-JDLEY7VU.mjs} +5 -4
  130. package/dist/cli/auth-UXKY6TCC.js +41 -0
  131. package/dist/cli/{billing-MTO2ZZ3G.js → billing-HXAXB3XZ.js} +152 -78
  132. package/dist/cli/{billing-26G44S5N.mjs → billing-SDHLBLMV.mjs} +84 -10
  133. package/dist/cli/{chunk-BHGII2D6.js → chunk-3ACMRZRV.js} +8 -6
  134. package/dist/cli/{chunk-S4Q2XCGM.mjs → chunk-3KZCFOZ6.mjs} +1 -1
  135. package/dist/cli/chunk-4KQ6SIBC.mjs +1038 -0
  136. package/dist/cli/{chunk-Z2GUFYT6.js → chunk-5LEAXRHV.js} +11 -11
  137. package/dist/cli/{chunk-XAYO3Q2X.mjs → chunk-6NNVJDVR.mjs} +7 -5
  138. package/dist/cli/{chunk-NMJ4D2WO.js → chunk-6PMCRYDA.js} +216 -91
  139. package/dist/cli/{chunk-OMM54B3X.mjs → chunk-6RNQGWFD.mjs} +16 -806
  140. package/dist/cli/{chunk-EBU7LQ64.js → chunk-6V7KVRR5.js} +3 -3
  141. package/dist/cli/{chunk-RDPTDDKX.js → chunk-7LFIMWMW.js} +2 -2
  142. package/dist/cli/chunk-7MJ6VI2T.mjs +19 -0
  143. package/dist/cli/chunk-7SWNPNML.js +1039 -0
  144. package/dist/cli/{chunk-CTRPCARW.mjs → chunk-7U3XNPOA.mjs} +267 -38
  145. package/dist/cli/{chunk-RBTTUYZZ.js → chunk-7VZHXVOQ.js} +1 -1
  146. package/dist/cli/{chunk-YVBWO647.mjs → chunk-AT636LLU.mjs} +2 -2
  147. package/dist/cli/{chunk-VTODQWQA.js → chunk-BGTPIMSM.js} +392 -261
  148. package/dist/cli/{chunk-KADIT5NV.mjs → chunk-CKSOY47X.mjs} +2 -2
  149. package/dist/cli/{chunk-7DH2FD7Y.mjs → chunk-D464JW3N.mjs} +5 -85
  150. package/dist/cli/chunk-D6DXOUTO.mjs +827 -0
  151. package/dist/cli/{describe-QW54QQNB.js → chunk-EM4TXCCI.js} +27 -44
  152. package/dist/cli/chunk-F6QV42MC.mjs +91 -0
  153. package/dist/cli/chunk-FKVYR3QA.mjs +1039 -0
  154. package/dist/cli/{chunk-LMAULZLF.js → chunk-GEGW5HPZ.js} +29 -27
  155. package/dist/cli/{chunk-M52JQSLK.js → chunk-HUB3CCM2.js} +13 -4
  156. package/dist/cli/{chunk-YHTQM5XI.js → chunk-I6VHOU5O.js} +27 -27
  157. package/dist/cli/{chunk-Z2XUZC34.js → chunk-IAYSWZDJ.js} +188 -76
  158. package/dist/cli/chunk-JF3TLIMQ.js +19 -0
  159. package/dist/cli/{chunk-HDFRJ4IT.mjs → chunk-JVHOS3K3.mjs} +177 -65
  160. package/dist/cli/{chunk-LAWEURCT.mjs → chunk-LFXLQEZN.mjs} +1 -1
  161. package/dist/cli/{chunk-2PBN4SZE.js → chunk-LROWFPGN.js} +16 -16
  162. package/dist/cli/{chunk-SLEUWLJE.mjs → chunk-MSY22NQW.mjs} +5 -5
  163. package/dist/cli/{chunk-LVJOFUU7.mjs → chunk-MUUQZIOF.mjs} +23 -49
  164. package/dist/cli/{chunk-2XHWOQF4.js → chunk-NBLSCHKK.js} +52 -50
  165. package/dist/cli/{chunk-526R2HON.mjs → chunk-NHRWZPWZ.mjs} +4 -2
  166. package/dist/cli/{chunk-KXMNM4AQ.js → chunk-NMU3VR5U.js} +4 -5
  167. package/dist/cli/{chunk-W3BE3CHK.mjs → chunk-O2RX5A66.mjs} +14 -12
  168. package/dist/cli/{chunk-UKFZT7RF.js → chunk-OMQ23IXL.js} +2 -2
  169. package/dist/cli/{chunk-LJTFZOHU.js → chunk-OPJAJBD4.js} +28 -54
  170. package/dist/cli/{chunk-MLFR6DVF.mjs → chunk-PHSEINNH.mjs} +5 -5
  171. package/dist/cli/{chunk-3DNKFH2G.mjs → chunk-PKHVSIJG.mjs} +19 -1
  172. package/dist/cli/{chunk-WIYWSYDS.mjs → chunk-Q2EIZKVX.mjs} +3 -3
  173. package/dist/cli/{chunk-VJAEA4CS.js → chunk-QJZ6WNWD.js} +8 -8
  174. package/dist/cli/{chunk-EIMG75BT.mjs → chunk-S3A4IXBQ.mjs} +4 -5
  175. package/dist/cli/{chunk-RZSTT45F.mjs → chunk-S42YIQNE.mjs} +13 -8
  176. package/dist/cli/{chunk-YULBYKAK.mjs → chunk-S6UAJR6V.mjs} +3 -3
  177. package/dist/cli/{chunk-G7L27KZB.mjs → chunk-TKAQR3IB.mjs} +31 -21
  178. package/dist/cli/{chunk-QWRHGGUH.js → chunk-UAJFWWZE.js} +20 -2
  179. package/dist/cli/{chunk-ZR2RLI4O.mjs → chunk-UDRPEZ5F.mjs} +223 -92
  180. package/dist/cli/{chunk-QGAWTBIR.js → chunk-ULK7JGGI.js} +511 -282
  181. package/dist/cli/chunk-V335HGX5.js +1038 -0
  182. package/dist/cli/{chunk-NH6JSJW6.mjs → chunk-V3MEA7NB.mjs} +7 -5
  183. package/dist/cli/{chunk-NRGGFVVB.js → chunk-VFGVXXZP.js} +37 -35
  184. package/dist/cli/chunk-VLEVVDWG.js +91 -0
  185. package/dist/cli/{chunk-R2GLGLDR.mjs → chunk-VPHN2HAL.mjs} +216 -91
  186. package/dist/cli/{chunk-CFUTOWE4.js → chunk-WG3KGAII.js} +8 -88
  187. package/dist/cli/{describe-TOMIAT37.mjs → chunk-WIY6U6GD.mjs} +19 -36
  188. package/dist/cli/{chunk-YS2YBSJB.js → chunk-XANRM3QA.js} +6 -6
  189. package/dist/cli/{chunk-KGQNIA37.js → chunk-XQCTG5OO.js} +44 -34
  190. package/dist/cli/{chunk-EJGBLXDZ.mjs → chunk-XZAFIOUQ.mjs} +1 -1
  191. package/dist/cli/chunk-Y4DPU4OM.js +827 -0
  192. package/dist/cli/chunk-YT5EODJO.js +920 -0
  193. package/dist/cli/{chunk-2WRO2UJ5.js → chunk-ZGQSFYD2.js} +56 -51
  194. package/dist/cli/{chunk-OJ6RUKVT.mjs → chunk-ZUK6LIJB.mjs} +10 -1
  195. package/dist/cli/command-docs.d.mts +5 -0
  196. package/dist/cli/command-docs.d.ts +5 -0
  197. package/dist/cli/command-docs.js +2 -2
  198. package/dist/cli/command-docs.mjs +1 -1
  199. package/dist/cli/{csv-6FNFYJUC.js → csv-C56GSCEU.js} +13 -12
  200. package/dist/cli/{csv-MDHT4BKK.mjs → csv-L54JS7TF.mjs} +4 -3
  201. package/dist/cli/{db-D6AMVOEW.js → db-MOGRMZTA.js} +30 -27
  202. package/dist/cli/{db-74O3RFWY.mjs → db-YVBUW2DX.mjs} +10 -7
  203. package/dist/cli/describe-KYEULWPG.js +48 -0
  204. package/dist/cli/describe-NLZKKT7C.mjs +48 -0
  205. package/dist/cli/describe-core.d.mts +153 -5
  206. package/dist/cli/describe-core.d.ts +153 -5
  207. package/dist/cli/describe-core.js +9 -5
  208. package/dist/cli/describe-core.mjs +10 -6
  209. package/dist/cli/{executions-N4FMGVSC.js → executions-A7L4ULPP.js} +9 -7
  210. package/dist/cli/{executions-PVSF4HUF.mjs → executions-ZBGSTT37.mjs} +7 -5
  211. package/dist/cli/{feedback-OB7NANZW.mjs → feedback-G6GU37FD.mjs} +6 -5
  212. package/dist/cli/{feedback-WFSPCAI2.js → feedback-OHLY7RYD.js} +10 -9
  213. package/dist/cli/getting-started.d.mts +6 -6
  214. package/dist/cli/getting-started.d.ts +6 -6
  215. package/dist/cli/getting-started.js +2 -2
  216. package/dist/cli/getting-started.mjs +1 -1
  217. package/dist/cli/git-P76N4NDQ.js +25 -0
  218. package/dist/cli/{git-FHUSPAEU.mjs → git-PKZW3QD5.mjs} +5 -4
  219. package/dist/cli/index.js +152 -142
  220. package/dist/cli/index.mjs +56 -46
  221. package/dist/cli/{legacy-noops-ECHNA5HN.js → legacy-noops-5LM3D724.js} +8 -7
  222. package/dist/cli/{legacy-noops-HMK4BSCI.mjs → legacy-noops-LJJYG6YA.mjs} +5 -4
  223. package/dist/cli/{monitors-U7VQ3EZM.mjs → monitors-72QV4ALO.mjs} +19 -6
  224. package/dist/cli/{monitors-XLLFNENH.js → monitors-UX3H5DC2.js} +103 -90
  225. package/dist/cli/{org-6H3USO5M.mjs → org-6RRRKOCP.mjs} +9 -6
  226. package/dist/cli/{org-QX6ER2HN.js → org-IF7ISUVB.js} +70 -67
  227. package/dist/cli/pages-3SU3HKL7.js +252 -0
  228. package/dist/cli/pages-DXDYYKEK.mjs +252 -0
  229. package/dist/cli/{play-MOB2WYOR.mjs → play-AHASKRLA.mjs} +512 -1508
  230. package/dist/cli/{play-WNB7HBZK.js → play-EUIUVLUH.js} +1090 -2086
  231. package/dist/cli/{providers-CXEZ23E2.mjs → providers-GWGE3WY3.mjs} +7 -5
  232. package/dist/cli/{providers-7M3DISKX.js → providers-V5WU4VXJ.js} +10 -8
  233. package/dist/cli/{quickstart-ZFCHKHF6.mjs → quickstart-OXKQ5ZST.mjs} +5 -4
  234. package/dist/cli/{quickstart-CLJT4AZL.js → quickstart-QP65I5PF.js} +13 -12
  235. package/dist/cli/run-6MM72IGL.mjs +443 -0
  236. package/dist/cli/run-F3FTYS56.js +443 -0
  237. package/dist/cli/search-OYKOOI7A.js +32 -0
  238. package/dist/cli/{search-FVYWYIC3.mjs → search-ZMEBVJZB.mjs} +8 -6
  239. package/dist/cli/{secrets-YRLKZQS5.js → secrets-ACND5N6A.js} +16 -14
  240. package/dist/cli/{secrets-WAMPTQFC.mjs → secrets-XRCJFF3L.mjs} +7 -5
  241. package/dist/cli/{sessions-IQ332BJH.mjs → sessions-RTK3CEW6.mjs} +5 -4
  242. package/dist/cli/{sessions-44J6WIVU.js → sessions-ZGRJPCFF.js} +6 -5
  243. package/dist/cli/{settings-4IJR2I2T.js → settings-PD5KWQQZ.js} +80 -77
  244. package/dist/cli/{settings-22S6NIFQ.mjs → settings-XDRBPRDE.mjs} +14 -11
  245. package/dist/cli/{setup-OZUPKKPW.mjs → setup-3HPYAOCC.mjs} +12 -10
  246. package/dist/cli/{setup-3FWSRNOX.js → setup-E2Q6PXHC.js} +53 -51
  247. package/dist/cli/{skills-5CW3NFP3.mjs → skills-OOQFMPOV.mjs} +7 -6
  248. package/dist/cli/skills-XYWV52KB.js +31 -0
  249. package/dist/cli/tools-ASCDZJCK.js +55 -0
  250. package/dist/cli/tools-C5ZT3UIC.mjs +55 -0
  251. package/dist/cli/update-5RQSEIFM.js +33 -0
  252. package/dist/cli/{update-IBULOV7P.mjs → update-ZWJTADIW.mjs} +9 -8
  253. package/dist/cli/{workflow-MVZT7MWT.mjs → workflow-23NMPAJ7.mjs} +10 -8
  254. package/dist/cli/{workflow-Z7FW6GT2.js → workflow-TCXU5YJV.js} +20 -18
  255. package/dist/client-DS2IWYZF.d.ts +10 -0
  256. package/dist/client-DTwnz_AW.d.mts +10 -0
  257. package/dist/{client-CYqG23K1.d.ts → client-core-B_6HAIBy.d.ts} +385 -12
  258. package/dist/{client-BbU5_j7n.d.mts → client-core-DzBSlY1U.d.mts} +385 -12
  259. package/dist/{compiler-manifest-CWz_sNOQ.d.mts → compiler-manifest-Dcyd3-Zd.d.mts} +43 -1
  260. package/dist/{compiler-manifest-CWz_sNOQ.d.ts → compiler-manifest-Dcyd3-Zd.d.ts} +43 -1
  261. package/dist/errors-d-sTrZhe.d.mts +142 -0
  262. package/dist/errors-yQwfgrcX.d.ts +142 -0
  263. package/dist/{index-BF2JB_Ug.d.mts → index-C6oXchjN.d.mts} +1 -1
  264. package/dist/{index-4NPXDzlD.d.ts → index-P56zy_z5.d.ts} +1 -1
  265. package/dist/index.d.mts +11 -147
  266. package/dist/index.d.ts +11 -147
  267. package/dist/index.js +15 -11
  268. package/dist/index.mjs +14 -10
  269. package/dist/install-integrity.json +141 -104
  270. package/dist/plays/bundle-play-file.d.mts +3 -3
  271. package/dist/plays/bundle-play-file.d.ts +3 -3
  272. package/dist/plays/bundle-play-file.mjs +2 -1
  273. package/dist/plays/play-description.d.mts +2 -2
  274. package/dist/plays/play-description.d.ts +2 -2
  275. package/dist/plays/play-description.js +2 -2
  276. package/dist/plays/play-description.mjs +1 -1
  277. package/dist/plays/run-request.d.mts +2 -2
  278. package/dist/plays/run-request.d.ts +2 -2
  279. package/dist/release.d.mts +1 -1
  280. package/dist/release.d.ts +1 -1
  281. package/dist/release.js +2 -2
  282. package/dist/release.mjs +1 -1
  283. package/dist/server.d.mts +17 -0
  284. package/dist/server.d.ts +17 -0
  285. package/dist/server.js +76 -0
  286. package/dist/server.mjs +76 -0
  287. package/package.json +6 -1
  288. package/dist/cli/auth-VDOCNR7H.js +0 -40
  289. package/dist/cli/chunk-PQUUWFXY.js +0 -95
  290. package/dist/cli/chunk-QJLKYYPX.js +0 -1710
  291. package/dist/cli/chunk-YBWAG72P.mjs +0 -95
  292. package/dist/cli/git-4PMBJSH5.js +0 -24
  293. package/dist/cli/search-4CCQCTVD.js +0 -30
  294. package/dist/cli/skills-LMNM6B5F.js +0 -30
  295. package/dist/cli/tools-AGBBDJ7P.js +0 -45
  296. package/dist/cli/tools-NOPBDRGW.mjs +0 -45
  297. package/dist/cli/update-U3W6USVD.js +0 -32
@@ -54,6 +54,7 @@ import type { CompiledRequestBatch } from './batch-runtime';
54
54
  import type { AnyBatchOperationStrategy } from './batching-types';
55
55
  import {
56
56
  getBillingEntitlementsViaAppRuntime,
57
+ writePageViaAppRuntime,
57
58
  type RuntimeApiResponseMeta,
58
59
  } from './app-runtime-api';
59
60
  import {
@@ -109,6 +110,8 @@ import {
109
110
  import { isImmutableRuntimeExecutionRecoveryOrigin } from './scheduler-backends/runtime-api-origin';
110
111
  import {
111
112
  isRetryableSecretResolutionStatus,
113
+ secretResolutionGotNoResponse,
114
+ secretResolutionOutageRetryDelayMs,
112
115
  secretResolutionRetryDecision,
113
116
  SECRET_RESOLUTION_MAX_ATTEMPTS,
114
117
  SECRET_RESOLUTION_ATTEMPT_TIMEOUT_MS,
@@ -136,6 +139,7 @@ import {
136
139
  type PacingResolver,
137
140
  type PlayExecutionGovernor,
138
141
  } from './governor/governor';
142
+ import { hasExplicitRowConcurrency } from './governor/policy';
139
143
  import {
140
144
  createRuntimeResourceGovernor,
141
145
  type RuntimeResourceGovernor,
@@ -214,6 +218,7 @@ import {
214
218
  createAlwaysFreshToolCallAdapter,
215
219
  createExternallyOwnedToolResultReceipts,
216
220
  createNativeBatchToolCallJob,
221
+ createMappedToolCallJob,
217
222
  createToolCallReceiptCohortJob,
218
223
  createRuntimeStepToolResultReceipts,
219
224
  createToolCallJob,
@@ -232,6 +237,7 @@ import {
232
237
  type ToolExecutionOutcome,
233
238
  } from './tool-execution-outcome';
234
239
  import {
240
+ toolExecuteConnectRetryDelayMs,
235
241
  TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS,
236
242
  TOOL_EXECUTE_RUNNER_ACCEPTS_HEADER,
237
243
  TOOL_EXECUTE_RUNNER_ACCEPTS_VALUE,
@@ -260,6 +266,7 @@ import {
260
266
  } from './tool-http-errors';
261
267
  import {
262
268
  describeTransportError,
269
+ transportFailureProvesRequestNeverSent,
263
270
  transportGatewayOriginForDiagnostic,
264
271
  } from './transport-error-diagnostics';
265
272
  import {
@@ -292,6 +299,8 @@ import {
292
299
  type PlaySecretPromise,
293
300
  type PlaySqlQuery,
294
301
  type PlayAuthoringRunScope,
302
+ type PlayAuthoringPageWriteOptions,
303
+ type PlayAuthoringPageWriteResult,
295
304
  type PlayAuthoringRuntimeContext,
296
305
  type PlayAuthoringRunStepsOptions,
297
306
  } from '../plays/authoring-contract';
@@ -402,6 +411,14 @@ import {
402
411
  type RuntimeMapHeapAdmissionSnapshot,
403
412
  } from './map-memory-limits';
404
413
  import { RuntimeMapRowFeed } from './runtime-map-row-feed';
414
+ import {
415
+ iterateSealedRuntimeArrayInput,
416
+ sealRuntimeArrayInput,
417
+ RuntimeArrayInputStoreUnavailableError,
418
+ isLosslessArrayInputValue,
419
+ type RuntimeArrayInputManifest,
420
+ type RuntimeArrayInputScope,
421
+ } from './array-input-store';
405
422
  import { ProviderRowRetryQueue } from './provider-row-retry-queue';
406
423
  import type { ProviderRowRetry } from './provider-row-retry-queue';
407
424
  import type { RuntimeMapRowFeedSnapshot } from './runtime-map-row-feed';
@@ -655,6 +672,7 @@ const authoredStepContext = new AsyncLocalStorage<{
655
672
  }>();
656
673
  const toolExecutionOverrides = new AsyncLocalStorage<{ force: boolean }>();
657
674
  const PROGRESS_HEARTBEAT_INTERVAL_MS = 1_000;
675
+
658
676
  const PURE_JS_HEARTBEAT_ROW_INTERVAL = 250;
659
677
  /** Executed-row sheet persistence chunking: rows AND bytes, whichever first. */
660
678
  const MAP_PERSIST_CHUNK_ROWS = 2_000;
@@ -1462,9 +1480,23 @@ function isUnmarkedExecutionGateway502(input: {
1462
1480
  }
1463
1481
  }
1464
1482
 
1465
- function waitForSecretResolutionRetry(ms: number): Promise<void> {
1483
+ function waitForSecretResolutionRetry(
1484
+ ms: number,
1485
+ signal?: AbortSignal,
1486
+ ): Promise<void> {
1487
+ signal?.throwIfAborted();
1466
1488
  if (ms <= 0) return Promise.resolve();
1467
- return new Promise<void>((resolve) => setTimeout(resolve, ms));
1489
+ return new Promise<void>((resolve, reject) => {
1490
+ const onAbort = () => {
1491
+ clearTimeout(timer);
1492
+ reject(signal?.reason);
1493
+ };
1494
+ const timer = setTimeout(() => {
1495
+ signal?.removeEventListener('abort', onAbort);
1496
+ resolve();
1497
+ }, ms);
1498
+ signal?.addEventListener('abort', onAbort, { once: true });
1499
+ });
1468
1500
  }
1469
1501
 
1470
1502
  function cancelRuntimeResponseBody(response: Response): void {
@@ -2720,6 +2752,7 @@ type ScalarPlayAuthoringRuntimeContext = Pick<
2720
2752
  | 'tools'
2721
2753
  | 'customerDb'
2722
2754
  | 'billing'
2755
+ | 'pages'
2723
2756
  | 'run'
2724
2757
  | 'tool'
2725
2758
  | 'step'
@@ -2730,6 +2763,36 @@ type ScalarPlayAuthoringRuntimeContext = Pick<
2730
2763
  | 'sleep'
2731
2764
  >;
2732
2765
 
2766
+ /** Text stays text on the wire, so only real binary pays the base64 overhead. */
2767
+ function encodePageContent(
2768
+ name: string,
2769
+ options: PlayAuthoringPageWriteOptions,
2770
+ ): { contentType: string } & (
2771
+ | { contentText: string }
2772
+ | { contentBase64: string }
2773
+ ) {
2774
+ const { content, contentType } = options;
2775
+ if (typeof content === 'string') {
2776
+ return {
2777
+ contentType: contentType ?? 'text/plain; charset=utf-8',
2778
+ contentText: content,
2779
+ };
2780
+ }
2781
+ if (content instanceof Uint8Array || content instanceof ArrayBuffer) {
2782
+ return {
2783
+ contentType: contentType ?? 'application/octet-stream',
2784
+ contentBase64: Buffer.from(content as Uint8Array).toString('base64'),
2785
+ };
2786
+ }
2787
+ const contentText = JSON.stringify(content);
2788
+ if (contentText === undefined) {
2789
+ throw new Error(
2790
+ `ctx.pages.write("${name}") requires content: a string, bytes, or a JSON value.`,
2791
+ );
2792
+ }
2793
+ return { contentType: contentType ?? 'application/json', contentText };
2794
+ }
2795
+
2733
2796
  export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2734
2797
  private rowStates = new Map<number, RowState>();
2735
2798
  private toolCallQueue: ToolCallRequest[] = [];
@@ -3023,6 +3086,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
3023
3086
  private readonly providerResponseBodyGate = createRuntimeReceiptHydrationGate(
3024
3087
  {
3025
3088
  occupancy: this.runtimeMemoryOccupancy,
3089
+ reservationCategory: 'providerBody',
3090
+ // Existing egress byte-work boundary, not a memory/output/width limit.
3091
+ cooperativeYieldBytes: RUNTIME_RECEIPT_WRITER_TARGET_BATCH_BYTES,
3026
3092
  reserve: (bytes) =>
3027
3093
  this.memoryLedger.register({ category: 'providerBody', bytes }),
3028
3094
  },
@@ -3355,6 +3421,40 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
3355
3421
  },
3356
3422
  };
3357
3423
 
3424
+ readonly pages = {
3425
+ write: async (
3426
+ name: string,
3427
+ options: PlayAuthoringPageWriteOptions,
3428
+ ): Promise<PlayAuthoringPageWriteResult> => {
3429
+ this.assertNoPendingDurableSuspension();
3430
+ if (typeof name !== 'string' || !name) {
3431
+ throw new Error('ctx.pages.write(name, options) requires a page name.');
3432
+ }
3433
+ if (typeof options?.public !== 'boolean') {
3434
+ throw new Error(
3435
+ `ctx.pages.write("${name}") requires public: true or false, so the write always says who can see the page.`,
3436
+ );
3437
+ }
3438
+ const result = await writePageViaAppRuntime(
3439
+ {
3440
+ baseUrl: this.#options.baseUrl ?? '',
3441
+ executorToken: this.#options.executorToken ?? '',
3442
+ vercelProtectionBypassToken:
3443
+ this.#options.vercelProtectionBypassToken,
3444
+ signal: this.#options.executionSignal,
3445
+ },
3446
+ {
3447
+ name,
3448
+ public: options.public,
3449
+ description: options.description,
3450
+ ...encodePageContent(name, options),
3451
+ },
3452
+ );
3453
+ for (const warning of result.warnings) this.log(warning);
3454
+ return result;
3455
+ },
3456
+ };
3457
+
3358
3458
  readonly secrets = {
3359
3459
  get: (name: string): PlaySecretPromise => {
3360
3460
  if (typeof name !== 'string' || !name.trim()) {
@@ -4415,21 +4515,28 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
4415
4515
  const url = `${this.#options.baseUrl.replace(/\/$/, '')}${PLAY_RUNTIME_API_COMPAT_PATH}`;
4416
4516
  const operationId = `ctx-secret-operation-${crypto.randomUUID()}`;
4417
4517
  const operationStartedAt = Date.now();
4418
- for (
4419
- let attempt = 1;
4420
- attempt <= SECRET_RESOLUTION_MAX_ATTEMPTS;
4421
- attempt += 1
4422
- ) {
4518
+ // Error responses spend the ordinary attempts. A lookup that gets no
4519
+ // response at all keeps retrying through a sandbox network cut, so the
4520
+ // run's cancellation must stop both the request and the wait.
4521
+ const executionSignal = this.#options.executionSignal;
4522
+ let respondedAttempts = 0;
4523
+ for (let attempt = 1; ; attempt += 1) {
4524
+ executionSignal?.throwIfAborted();
4423
4525
  const requestId = `ctx-secret-${crypto.randomUUID()}`;
4424
4526
  const attemptStartedAt = Date.now();
4527
+ // Bound the control-plane call so a stalled connection aborts and is
4528
+ // caught below as a transport failure instead of hanging the tool
4529
+ // call "1 in flight" forever.
4530
+ const attemptTimeout = AbortSignal.timeout(
4531
+ SECRET_RESOLUTION_ATTEMPT_TIMEOUT_MS,
4532
+ );
4425
4533
  let response: Response;
4426
4534
  try {
4427
4535
  response = await fetch(url, {
4428
4536
  method: 'POST',
4429
- // Bound the control-plane call so a stalled connection aborts and is
4430
- // caught below as a transport failure instead of hanging the tool
4431
- // call "1 in flight" forever.
4432
- signal: AbortSignal.timeout(SECRET_RESOLUTION_ATTEMPT_TIMEOUT_MS),
4537
+ signal: executionSignal
4538
+ ? AbortSignal.any([executionSignal, attemptTimeout])
4539
+ : attemptTimeout,
4433
4540
  headers: {
4434
4541
  Authorization: `Bearer ${this.#options.executorToken}`,
4435
4542
  'Content-Type': 'application/json',
@@ -4445,11 +4552,23 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
4445
4552
  }),
4446
4553
  });
4447
4554
  } catch (error) {
4555
+ executionSignal?.throwIfAborted();
4448
4556
  const diagnostic = describeTransportError(error);
4449
- const retry = secretResolutionRetryDecision({
4557
+ const ladder = secretResolutionRetryDecision({
4450
4558
  attempt,
4451
4559
  retryAfter: null,
4452
4560
  });
4561
+ const outageRetryDelayMs =
4562
+ !ladder.retry && secretResolutionGotNoResponse(error)
4563
+ ? secretResolutionOutageRetryDelayMs({
4564
+ attempt,
4565
+ elapsedMs: Date.now() - operationStartedAt,
4566
+ })
4567
+ : null;
4568
+ const retry =
4569
+ outageRetryDelayMs === null
4570
+ ? ladder
4571
+ : { ...ladder, retry: true, retryDelayMs: outageRetryDelayMs };
4453
4572
  this.runtimeLog(
4454
4573
  `[runtime.secret_resolution_failure] ${JSON.stringify({
4455
4574
  stage: 'transport',
@@ -4471,13 +4590,20 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
4471
4590
  `Secret ${secret.name} resolution transport failed (request_id=${requestId}): ${diagnostic.message ?? 'unknown transport error'}`,
4472
4591
  );
4473
4592
  }
4474
- await waitForSecretResolutionRetry(retry.retryDelayMs);
4593
+ await waitForSecretResolutionRetry(
4594
+ retry.retryDelayMs,
4595
+ executionSignal,
4596
+ );
4475
4597
  continue;
4476
4598
  }
4477
4599
 
4478
4600
  const retryAfter = response.headers.get('retry-after');
4601
+ respondedAttempts += 1;
4479
4602
  const retry = isRetryableSecretResolutionStatus(response.status)
4480
- ? secretResolutionRetryDecision({ attempt, retryAfter })
4603
+ ? secretResolutionRetryDecision({
4604
+ attempt: respondedAttempts,
4605
+ retryAfter,
4606
+ })
4481
4607
  : NO_SECRET_RESOLUTION_RETRY;
4482
4608
  const responseDiagnostic = {
4483
4609
  stage: response.ok ? 'invalid_response' : 'http_response',
@@ -4510,7 +4636,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
4510
4636
  );
4511
4637
  cancelRuntimeResponseBody(response);
4512
4638
  if (retry.retry) {
4513
- await waitForSecretResolutionRetry(retry.retryDelayMs);
4639
+ await waitForSecretResolutionRetry(
4640
+ retry.retryDelayMs,
4641
+ executionSignal,
4642
+ );
4514
4643
  continue;
4515
4644
  }
4516
4645
  throw new Error(
@@ -5422,6 +5551,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
5422
5551
  private async withProviderResponseBodyTurn<T>(
5423
5552
  response: Response,
5424
5553
  read: (turn: RuntimeReceiptHydrationTurn) => Promise<T>,
5554
+ signal?: AbortSignal,
5425
5555
  ): Promise<{ value: T; waitMs: number }> {
5426
5556
  const queuedAtMs = Date.now();
5427
5557
  const expectedBytes = runtimeProviderResponseBodyExpectedBytes({
@@ -5432,9 +5562,30 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
5432
5562
  this.#options.executionGatewayBaseUrl?.trim(),
5433
5563
  ),
5434
5564
  });
5435
- const turn = await this.providerResponseBodyGate.acquire({
5436
- ...(expectedBytes === null ? {} : { expectedBytes }),
5437
- });
5565
+ let turn: RuntimeReceiptHydrationTurn;
5566
+ try {
5567
+ turn = await this.providerResponseBodyGate.acquire({
5568
+ ...(expectedBytes === null ? {} : { expectedBytes }),
5569
+ ...(signal === undefined ? {} : { signal }),
5570
+ });
5571
+ } catch (error) {
5572
+ // Headers already arrived: an abandoned waiter still owns this unread
5573
+ // stream. Cancel it without replacing the original admission/abort error.
5574
+ const reportCancelFailure = (cancelError: unknown) => {
5575
+ this.runtimeLog(
5576
+ `[runtime-provider-response] unread_body_cancel_failed error=${cancelError instanceof Error ? cancelError.name : 'UnknownError'}`,
5577
+ { level: 'error' },
5578
+ );
5579
+ };
5580
+ try {
5581
+ // Stream cleanup may remain pending (for example, a tee's other branch).
5582
+ // Start it, but do not hold the caller's abort or provider permit on it.
5583
+ void response.body?.cancel(error).catch(reportCancelFailure);
5584
+ } catch (cancelError) {
5585
+ reportCancelFailure(cancelError);
5586
+ }
5587
+ throw error;
5588
+ }
5438
5589
  const waitMs = Math.max(0, Date.now() - queuedAtMs);
5439
5590
  try {
5440
5591
  return { value: await read(turn), waitMs };
@@ -7475,7 +7626,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
7475
7626
  return current;
7476
7627
  }
7477
7628
  if (isSerializedToolExecuteResult(current)) {
7478
- return deserializeToolExecuteResult(current);
7629
+ // Cells store the canonical dataset-valued result; a later column must
7630
+ // see the same edition getters as the column that executed the tool.
7631
+ return projectToolResultListGetters(
7632
+ deserializeToolExecuteResult(current),
7633
+ this.currentRuntimeContract.semantics.tools.listGetters,
7634
+ );
7479
7635
  }
7480
7636
  if (isSerializedPlayDatasetCell(current)) {
7481
7637
  return deserializePlayDatasetCell(current, visit);
@@ -8494,85 +8650,94 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8494
8650
  : Array.isArray(items)
8495
8651
  ? items
8496
8652
  : null;
8497
- const csvSource =
8653
+ const replayMapInvocationId = `${normalizedMapNamespace}:${this.mapInvocationIndex}`;
8654
+ const replayMapFrame = this.checkpoint.mapFrames?.[replayMapInvocationId];
8655
+ const authoredCsvSource =
8498
8656
  (isPlayDataset(items) && items.datasetKind === 'csv') ||
8499
8657
  residentSourceRows?.some(
8500
8658
  (_row, index) =>
8501
8659
  getPlayDatasetSourceRowIdentityAt(residentSourceRows, index) !==
8502
8660
  undefined,
8503
8661
  ) === true;
8504
- const csvOccurrenceIdentityRequired =
8505
- csvSource &&
8506
- this.currentRuntimeSemantics.datasets.csvRowIdentity === 'source_row';
8507
- const rowIdentityMode = csvOccurrenceIdentityRequired
8508
- ? 'source_row'
8509
- : (options?.rowIdentity ??
8510
- (options?.key !== undefined
8511
- ? 'key'
8512
- : csvSource
8513
- ? this.currentRuntimeSemantics.datasets.csvRowIdentity
8514
- : 'key'));
8515
- const useSourceRowIdentity = rowIdentityMode === 'source_row';
8516
8662
  const userKeyOption = options?.key;
8517
- let explicitKeyResolver:
8518
- | ((row: Record<string, unknown>, index: number) => string)
8519
- | null = null;
8520
- if (useSourceRowIdentity) {
8521
- explicitKeyResolver = (row, index) => {
8522
- const source = getPlayDatasetSourceRowIdentity(row);
8523
- const inputRow = Object.fromEntries(
8524
- Object.entries(toSerializableCsvAliasedRow(row)).filter(
8525
- ([field]) => !field.startsWith('__deepline'),
8526
- ),
8527
- );
8528
- // Position distinguishes equal records; the input digest prevents a
8529
- // changed file at the same path from replaying an earlier row's output.
8530
- return JSON.stringify([
8531
- 'source-row-v1',
8532
- source?.datasetId ??
8533
- (isPlayDataset(items) ? items.datasetId : normalizedMapNamespace),
8534
- source?.rowIndex ?? index,
8535
- stableDigest(stableStringify(inputRow)),
8536
- ]);
8537
- };
8538
- } else if (userKeyOption !== undefined) {
8539
- explicitKeyResolver = (row, index) => {
8540
- const inputRow = stripFieldOutputs(row);
8541
- const raw =
8542
- typeof userKeyOption === 'function'
8543
- ? (
8544
- userKeyOption as (
8545
- row: Record<string, unknown>,
8546
- index: number,
8547
- ) => string | number | readonly unknown[]
8548
- )(inputRow, index)
8549
- : Array.isArray(userKeyOption)
8550
- ? userKeyOption.map((fieldName) => inputRow[fieldName])
8551
- : inputRow[String(userKeyOption)];
8552
- const parts = Array.isArray(raw) ? raw : [raw];
8553
- if (parts.some((part) => part === null || part === undefined)) {
8554
- throw new Error(
8555
- `ctx.dataset("${normalizedMapNamespace}") key returned null or undefined for row ${index}. ` +
8556
- 'Return a non-empty string or number derived from a stable input column (e.g. row.email ?? row.domain).',
8663
+ let rowIdentityMode: 'source_row' | 'key' = 'key';
8664
+ let useSourceRowIdentity = false;
8665
+ const setSourceIdentityMode = (
8666
+ csvSource: boolean,
8667
+ ): ((row: Record<string, unknown>, index: number) => string) | null => {
8668
+ const csvOccurrenceIdentityRequired =
8669
+ csvSource &&
8670
+ this.currentRuntimeSemantics.datasets.csvRowIdentity === 'source_row';
8671
+ rowIdentityMode = csvOccurrenceIdentityRequired
8672
+ ? 'source_row'
8673
+ : (options?.rowIdentity ??
8674
+ (options?.key !== undefined
8675
+ ? 'key'
8676
+ : csvSource
8677
+ ? this.currentRuntimeSemantics.datasets.csvRowIdentity
8678
+ : 'key'));
8679
+ useSourceRowIdentity = rowIdentityMode === 'source_row';
8680
+ if (useSourceRowIdentity) {
8681
+ return (row, index) => {
8682
+ const source = getPlayDatasetSourceRowIdentity(row);
8683
+ const inputRow = Object.fromEntries(
8684
+ Object.entries(toSerializableCsvAliasedRow(row)).filter(
8685
+ ([field]) => !field.startsWith('__deepline'),
8686
+ ),
8557
8687
  );
8558
- }
8559
- const normalizedParts = parts.map((part) => {
8560
- if (typeof part === 'number') {
8561
- return Number.isFinite(part) ? String(part) : '';
8688
+ // Position distinguishes equal records; the input digest prevents a
8689
+ // changed file at the same path from replaying an earlier row's output.
8690
+ return JSON.stringify([
8691
+ 'source-row-v1',
8692
+ source?.datasetId ??
8693
+ (isPlayDataset(items) ? items.datasetId : normalizedMapNamespace),
8694
+ source?.rowIndex ?? index,
8695
+ stableDigest(stableStringify(inputRow)),
8696
+ ]);
8697
+ };
8698
+ } else if (userKeyOption !== undefined) {
8699
+ return (row, index) => {
8700
+ const inputRow = stripFieldOutputs(row);
8701
+ const raw =
8702
+ typeof userKeyOption === 'function'
8703
+ ? (
8704
+ userKeyOption as (
8705
+ row: Record<string, unknown>,
8706
+ index: number,
8707
+ ) => string | number | readonly unknown[]
8708
+ )(inputRow, index)
8709
+ : Array.isArray(userKeyOption)
8710
+ ? userKeyOption.map((fieldName) => inputRow[fieldName])
8711
+ : inputRow[String(userKeyOption)];
8712
+ const parts = Array.isArray(raw) ? raw : [raw];
8713
+ if (parts.some((part) => part === null || part === undefined)) {
8714
+ throw new Error(
8715
+ `ctx.dataset("${normalizedMapNamespace}") key returned null or undefined for row ${index}. ` +
8716
+ 'Return a non-empty string or number derived from a stable input column (e.g. row.email ?? row.domain).',
8717
+ );
8562
8718
  }
8563
- return String(part).trim();
8564
- });
8565
- if (normalizedParts.some((part) => !part)) {
8566
- throw new Error(
8567
- `ctx.dataset("${normalizedMapNamespace}") key returned an empty value for row ${index}. ` +
8568
- 'Return a non-empty string or finite number derived from a stable input column.',
8569
- );
8570
- }
8571
- return normalizedParts.length === 1
8572
- ? normalizedParts[0]!
8573
- : JSON.stringify(normalizedParts);
8574
- };
8575
- }
8719
+ const normalizedParts = parts.map((part) => {
8720
+ if (typeof part === 'number') {
8721
+ return Number.isFinite(part) ? String(part) : '';
8722
+ }
8723
+ return String(part).trim();
8724
+ });
8725
+ if (normalizedParts.some((part) => !part)) {
8726
+ throw new Error(
8727
+ `ctx.dataset("${normalizedMapNamespace}") key returned an empty value for row ${index}. ` +
8728
+ 'Return a non-empty string or finite number derived from a stable input column.',
8729
+ );
8730
+ }
8731
+ return normalizedParts.length === 1
8732
+ ? normalizedParts[0]!
8733
+ : JSON.stringify(normalizedParts);
8734
+ };
8735
+ }
8736
+ return null;
8737
+ };
8738
+ let explicitKeyResolver = setSourceIdentityMode(
8739
+ replayMapFrame?.arrayInput?.hasSourceIdentities ?? authoredCsvSource,
8740
+ );
8576
8741
 
8577
8742
  const rowIdentity = (row: Record<string, unknown>, index = 0) => {
8578
8743
  const runtimeKey = resolveMapRowOutcomeKey(row);
@@ -8612,17 +8777,115 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8612
8777
  const mapMemoryLimits = resolveRuntimeMapMemoryLimits(
8613
8778
  this.#options.runtimeMapMemoryLimits,
8614
8779
  );
8780
+ // A derived key is "namespace:<64 hex chars>". Admission may rebind the
8781
+ // namespace after staging, so reserve its validated maximum up front.
8782
+ // normalizeTableNamespace emits ASCII and caps it at this many bytes.
8783
+ const implicitKeyReservationBytesPerRow =
8784
+ 96 + MAP_KEY_NAMESPACE_MAX_LENGTH + 1 + 64;
8785
+ // Staged explicit-key dedupe retains a SHA-256 token, never an unbounded
8786
+ // authored key string. The same digest primitive already names row keys.
8787
+ const explicitKeyReservationBytesPerRow = 96 + 64;
8615
8788
  const materializationMemoryTracker = createRuntimeMapMaterializationTracker(
8616
8789
  {
8617
8790
  mapName: normalizedMapNamespace,
8618
8791
  budgetBytes: mapMemoryLimits.materializedBudgetBytes,
8619
8792
  },
8620
8793
  );
8621
- const shouldStreamRuntimeBackedDataset =
8622
- isPlayDataset<T>(items) &&
8794
+ // Keep transport response validation at its real boundary. The maps below
8795
+ // are local admission state, never a synthetic aggregate wire response.
8796
+ const admitMapRows = async (
8797
+ rows: Record<string, unknown>[],
8798
+ tableNamespace: string,
8799
+ mapContext: Parameters<NonNullable<ContextOptions['onMapStart']>>[2],
8800
+ admissionBudgetBytes = mapMemoryLimits.materializedBudgetBytes,
8801
+ ) => {
8802
+ // Only transport paging creates cumulative response retention. Preserve
8803
+ // the legacy single callback's existing checks. Streamed sources pass
8804
+ // their reserved page budget; materialized admission uses the global one.
8805
+ const retainedAdmissionMemory = this.#options.onMapStartPages
8806
+ ? createRuntimeMapMaterializationTracker({
8807
+ mapName: normalizedMapNamespace,
8808
+ budgetBytes: admissionBudgetBytes,
8809
+ })
8810
+ : undefined;
8811
+ const pages = this.#options.onMapStartPages
8812
+ ? this.#options.onMapStartPages(rows, tableNamespace, mapContext)
8813
+ : (async function* (start: NonNullable<ContextOptions['onMapStart']>) {
8814
+ yield await start(rows, tableNamespace, mapContext);
8815
+ })(this.#options.onMapStart!);
8816
+ const pendingRowsByKey = new Map<string, Record<string, unknown>>();
8817
+ const completedRowsByKey = new Map<string, Record<string, unknown>>();
8818
+ const previousCellsByKey:
8819
+ | ReturnType<typeof assertDatasetAdmissionHistory>
8820
+ | undefined =
8821
+ this.currentRuntimeContract.datasetRowInput === 'current_run'
8822
+ ? new Map()
8823
+ : undefined;
8824
+ let resolvedNamespace: string | undefined;
8825
+ for await (const result of pages) {
8826
+ const pageNamespace = normalizeTableNamespace(result.tableNamespace);
8827
+ if (resolvedNamespace && resolvedNamespace !== pageNamespace) {
8828
+ throw new Error('Dataset admission pages changed table namespace.');
8829
+ }
8830
+ resolvedNamespace = pageNamespace;
8831
+ // Legacy callbacks may resolve an alias and return unstamped rows.
8832
+ // Derive their fallback keys in that resolved namespace, as before.
8833
+ resolvedTableNamespace = pageNamespace;
8834
+ const history = previousCellsByKey
8835
+ ? assertDatasetAdmissionHistory(
8836
+ result,
8837
+ Math.floor(
8838
+ admissionBudgetBytes /
8839
+ NODE_RUNTIME_MAP_MATERIALIZED_MEMORY_MULTIPLIER,
8840
+ ),
8841
+ )
8842
+ : undefined;
8843
+ for (const [key, cells] of history ?? []) {
8844
+ if (!previousCellsByKey!.has(key)) {
8845
+ retainedAdmissionMemory?.track({ previousCells: cells });
8846
+ previousCellsByKey!.set(key, cells);
8847
+ }
8848
+ }
8849
+ for (const [index, row] of result.pendingRows.entries()) {
8850
+ const key =
8851
+ resolveMapRowOutcomeKey(row) ??
8852
+ rowIdentity(row, (mapContext.inputOffset ?? 0) + index);
8853
+ if (
8854
+ key &&
8855
+ (!pendingRowsByKey.has(key) || !this.#options.onMapStartPages)
8856
+ ) {
8857
+ retainedAdmissionMemory?.track(row);
8858
+ pendingRowsByKey.set(key, row);
8859
+ }
8860
+ }
8861
+ for (const row of result.completedRows ?? []) {
8862
+ const key = resolveMapRowOutcomeKey(row) ?? rowIdentity(row, 0);
8863
+ if (
8864
+ key &&
8865
+ (!completedRowsByKey.has(key) || !this.#options.onMapStartPages)
8866
+ ) {
8867
+ retainedAdmissionMemory?.track(row);
8868
+ completedRowsByKey.set(key, row);
8869
+ }
8870
+ }
8871
+ }
8872
+ if (!resolvedNamespace) {
8873
+ throw new Error('Dataset admission did not return a response page.');
8874
+ }
8875
+ return {
8876
+ tableNamespace: resolvedNamespace,
8877
+ pendingRowsByKey,
8878
+ completedRowsByKey,
8879
+ previousCellsByKey,
8880
+ };
8881
+ };
8882
+ // A pre-paging array checkpoint owns its pending authored inputs. Finish
8883
+ // that invocation on its original replay path even if the authored array
8884
+ // changed while the run was parked.
8885
+ const hasRuntimeSheetControls =
8623
8886
  this.#options.runtimeSheetBackedMapDatasets === true &&
8624
8887
  Boolean(
8625
- this.#options.onMapStart &&
8888
+ (this.#options.onMapStart || this.#options.onMapStartPages) &&
8626
8889
  this.#options.onMapRowsCompleted &&
8627
8890
  this.#options.baseUrl &&
8628
8891
  this.#options.executorToken &&
@@ -8633,8 +8896,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8633
8896
  // rebuilt in authored call order. Look up a previously suspended plan now,
8634
8897
  // before a stateful source query can decide that a sibling action is no
8635
8898
  // longer eligible because an earlier action already completed.
8636
- const replayMapInvocationId = `${normalizedMapNamespace}:${this.mapInvocationIndex}`;
8637
- const replayMapFrame = this.checkpoint.mapFrames?.[replayMapInvocationId];
8638
8899
  const canReadPersistedMapResults = Boolean(
8639
8900
  this.#options.onMapRowsCompleted &&
8640
8901
  this.#options.dbSessionStrategy &&
@@ -8643,6 +8904,153 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8643
8904
  this.#options.playName &&
8644
8905
  this.#options.runId,
8645
8906
  );
8907
+ const sourceArrayEstimate = Array.isArray(items)
8908
+ ? estimateRuntimeMapRowsMemory(items)
8909
+ : null;
8910
+ let sealedArrayInput: Extract<
8911
+ RuntimeArrayInputManifest,
8912
+ { state: 'sealed' }
8913
+ > | null = null;
8914
+ const arrayInputStore = this.#options.runtimeArrayInputStore;
8915
+ const arrayInputScope: RuntimeArrayInputScope = {
8916
+ playName: this.#options.playName ?? '',
8917
+ runId: this.#options.runId ?? '',
8918
+ tableNamespace: resolvedTableNamespace,
8919
+ mapInvocationId: replayMapInvocationId,
8920
+ };
8921
+ if (
8922
+ replayMapFrame?.arrayInput &&
8923
+ (!Array.isArray(items) ||
8924
+ !hasRuntimeSheetControls ||
8925
+ !canReadPersistedMapResults ||
8926
+ !arrayInputStore)
8927
+ ) {
8928
+ throw new Error(
8929
+ `Dataset ${resolvedTableNamespace} cannot replay its sealed array input without the runtime sheet and array input reader.`,
8930
+ );
8931
+ }
8932
+ if (
8933
+ Array.isArray(items) &&
8934
+ !(replayMapFrame?.suspendedInputRows && !replayMapFrame.arrayInput) &&
8935
+ hasRuntimeSheetControls &&
8936
+ canReadPersistedMapResults &&
8937
+ arrayInputStore
8938
+ ) {
8939
+ let manifest: RuntimeArrayInputManifest;
8940
+ let storeUnavailable = false;
8941
+ try {
8942
+ // A sealed source may predate the first checkpoint. Probe even if the
8943
+ // current authored array is small or empty, so that intent survives a
8944
+ // crash between sealing and the first map frame write.
8945
+ manifest = await arrayInputStore.getManifest(arrayInputScope);
8946
+ } catch (error) {
8947
+ if (!(error instanceof RuntimeArrayInputStoreUnavailableError))
8948
+ throw error;
8949
+ if (replayMapFrame?.arrayInput) {
8950
+ throw new Error(
8951
+ `Dataset ${resolvedTableNamespace} requires its sealed array input, but this receiver cannot read array input pages.`,
8952
+ { cause: error },
8953
+ );
8954
+ }
8955
+ manifest = { state: 'absent' };
8956
+ storeUnavailable = true;
8957
+ }
8958
+ // The sealed snapshot is the source of truth after a crash, including an
8959
+ // explicit false. The regenerated authored array can be empty or carry
8960
+ // different sidecars, so choose keys only after reading this manifest.
8961
+ if (manifest.state === 'sealed') {
8962
+ explicitKeyResolver = setSourceIdentityMode(
8963
+ manifest.hasSourceIdentities,
8964
+ );
8965
+ }
8966
+ if (manifest.state === 'absent' && replayMapFrame?.arrayInput) {
8967
+ throw new Error(
8968
+ `Dataset ${resolvedTableNamespace} sealed array input is missing on replay.`,
8969
+ );
8970
+ }
8971
+ const stagingCandidate =
8972
+ !storeUnavailable &&
8973
+ (manifest.state !== 'absent' ||
8974
+ (this.#options.runtimeArrayInputStagingEnabled === true &&
8975
+ sourceArrayEstimate !== null &&
8976
+ sourceArrayEstimate.estimatedResidentBytes <=
8977
+ mapMemoryLimits.materializedBudgetBytes &&
8978
+ sourceArrayEstimate.estimatedResidentBytes * 2 >
8979
+ mapMemoryLimits.materializedBudgetBytes));
8980
+ const sourceCanStageLosslessly =
8981
+ manifest.state === 'sealed' ||
8982
+ !stagingCandidate ||
8983
+ items.every(
8984
+ (item, index) =>
8985
+ isLosslessArrayInputValue(item) &&
8986
+ isLosslessArrayInputValue(
8987
+ getPlayDatasetSourceRowIdentityAt(items, index) ?? null,
8988
+ ),
8989
+ );
8990
+ if (manifest.state === 'staging' && !sourceCanStageLosslessly) {
8991
+ throw new Error(
8992
+ `Dataset ${resolvedTableNamespace} source changed before its array input was sealed. Retry with the original source.`,
8993
+ );
8994
+ }
8995
+ const needsStaging = stagingCandidate && sourceCanStageLosslessly;
8996
+ if (needsStaging) {
8997
+ const stageKeyReservationBytes = !useSourceRowIdentity
8998
+ ? items.length *
8999
+ (explicitKeyResolver
9000
+ ? explicitKeyReservationBytesPerRow
9001
+ : implicitKeyReservationBytesPerRow)
9002
+ : 0;
9003
+ const stageResidentBytes =
9004
+ (sourceArrayEstimate?.estimatedResidentBytes ?? 0) +
9005
+ stageKeyReservationBytes;
9006
+ if (
9007
+ manifest.state !== 'sealed' &&
9008
+ sourceArrayEstimate &&
9009
+ stageResidentBytes >= mapMemoryLimits.materializedBudgetBytes
9010
+ ) {
9011
+ throw new RuntimeMapMemoryLimitError({
9012
+ mapName: normalizedMapNamespace,
9013
+ phase: 'materialize',
9014
+ estimate: {
9015
+ ...sourceArrayEstimate,
9016
+ estimatedResidentBytes: stageResidentBytes,
9017
+ },
9018
+ budgetBytes: mapMemoryLimits.materializedBudgetBytes,
9019
+ });
9020
+ }
9021
+ try {
9022
+ sealedArrayInput = await sealRuntimeArrayInput({
9023
+ scope: arrayInputScope,
9024
+ rows: items,
9025
+ store: arrayInputStore,
9026
+ manifest,
9027
+ maxPageMemoryBytes:
9028
+ manifest.state === 'sealed'
9029
+ ? undefined
9030
+ : Math.floor(
9031
+ (mapMemoryLimits.materializedBudgetBytes -
9032
+ stageResidentBytes) /
9033
+ 4,
9034
+ ),
9035
+ normalize: (item, index) => ({
9036
+ data: sanitizePostgresJsonValue(
9037
+ cloneCsvAliasedRow(this.toOutputRow(item)),
9038
+ ) as Record<string, unknown>,
9039
+ sourceIdentity: getPlayDatasetSourceRowIdentityAt(items, index),
9040
+ }),
9041
+ });
9042
+ } catch (error) {
9043
+ if (
9044
+ !(error instanceof RuntimeArrayInputStoreUnavailableError) ||
9045
+ manifest.state !== 'absent'
9046
+ )
9047
+ throw error;
9048
+ }
9049
+ }
9050
+ }
9051
+ const shouldStreamRuntimeBackedDataset =
9052
+ (isPlayDataset<T>(items) || sealedArrayInput !== null) &&
9053
+ hasRuntimeSheetControls;
8646
9054
  // A suspended map's admitted pending inputs remain authoritative even
8647
9055
  // when a stateful authored source returns fewer rows after a wake.
8648
9056
  const suspendedInputRows = shouldStreamRuntimeBackedDataset
@@ -8698,10 +9106,77 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8698
9106
  const isVisibleTerminalRow = (row: PersistableMapRow): boolean =>
8699
9107
  visibility.isVisible(row.key);
8700
9108
  if (shouldStreamRuntimeBackedDataset) {
9109
+ // A caller keeps an array alive across this await. Reserve its complete
9110
+ // resident estimate before admitting any row, then divide the remaining
9111
+ // budget among the active page, sheet-admission copies, retained row
9112
+ // outcomes, and the optional immediate result cache. A cursor has no
9113
+ // resident source.
9114
+ const arraySourceMemoryEstimate = sealedArrayInput
9115
+ ? sourceArrayEstimate
9116
+ : null;
9117
+ if (
9118
+ arraySourceMemoryEstimate &&
9119
+ arraySourceMemoryEstimate.estimatedResidentBytes >
9120
+ mapMemoryLimits.materializedBudgetBytes
9121
+ ) {
9122
+ throw new RuntimeMapMemoryLimitError({
9123
+ mapName: normalizedMapNamespace,
9124
+ phase: 'materialize',
9125
+ estimate: arraySourceMemoryEstimate,
9126
+ budgetBytes: mapMemoryLimits.materializedBudgetBytes,
9127
+ });
9128
+ }
9129
+ // Both explicit and implicit first-wins keys survive page settlement.
9130
+ // Reserve their bounded index apart from transient page allocations.
9131
+ const keyIndexBudgetBytes =
9132
+ sealedArrayInput && !useSourceRowIdentity
9133
+ ? sealedArrayInput.sourceRows *
9134
+ (explicitKeyResolver
9135
+ ? explicitKeyReservationBytesPerRow
9136
+ : implicitKeyReservationBytesPerRow)
9137
+ : 0;
9138
+ if (
9139
+ arraySourceMemoryEstimate &&
9140
+ arraySourceMemoryEstimate.estimatedResidentBytes +
9141
+ keyIndexBudgetBytes >=
9142
+ mapMemoryLimits.materializedBudgetBytes
9143
+ ) {
9144
+ throw new RuntimeMapMemoryLimitError({
9145
+ mapName: normalizedMapNamespace,
9146
+ phase: 'materialize',
9147
+ estimate: {
9148
+ ...arraySourceMemoryEstimate,
9149
+ estimatedResidentBytes:
9150
+ arraySourceMemoryEstimate.estimatedResidentBytes +
9151
+ keyIndexBudgetBytes,
9152
+ },
9153
+ budgetBytes: mapMemoryLimits.materializedBudgetBytes,
9154
+ });
9155
+ }
9156
+ const pageBudgetBytes = arraySourceMemoryEstimate
9157
+ ? Math.max(
9158
+ 1,
9159
+ Math.floor(
9160
+ (mapMemoryLimits.materializedBudgetBytes -
9161
+ arraySourceMemoryEstimate.estimatedResidentBytes -
9162
+ keyIndexBudgetBytes) /
9163
+ 4,
9164
+ ),
9165
+ )
9166
+ : mapMemoryLimits.materializedBudgetBytes;
8701
9167
  // Net-new admission happens one source page at a time. Its progress and
8702
9168
  // returned dataset describe admitted rows, not every provider/CSV row
8703
9169
  // supplied to this run, so accumulate the admitted count below.
8704
- totalInputCount = options?.mode === 'net_new' ? 0 : await items.count();
9170
+ totalInputCount =
9171
+ options?.mode === 'net_new'
9172
+ ? 0
9173
+ : sealedArrayInput
9174
+ ? sealedArrayInput.sourceRows
9175
+ : isPlayDataset(items)
9176
+ ? await items.count()
9177
+ : (() => {
9178
+ throw new Error('Runtime-backed map source is missing.');
9179
+ })();
8705
9180
  const mapScope = this.createMapExecutionScope({
8706
9181
  logicalNamespace: normalizedMapNamespace,
8707
9182
  artifactTableNamespace: resolvedTableNamespace,
@@ -8713,6 +9188,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8713
9188
  logicalNamespace: mapScope.logicalNamespace,
8714
9189
  artifactTableNamespace: mapScope.artifactTableNamespace,
8715
9190
  status: 'running',
9191
+ arrayInput: sealedArrayInput
9192
+ ? { hasSourceIdentities: sealedArrayInput.hasSourceIdentities }
9193
+ : undefined,
8716
9194
  totalRows: totalInputCount,
8717
9195
  completedRowKeys: [],
8718
9196
  pendingRowKeys: [],
@@ -8735,6 +9213,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8735
9213
  });
8736
9214
 
8737
9215
  const seenExplicitKeys = new Set<string>();
9216
+ let seenExplicitKeyBytes = 0;
9217
+ const seenImplicitRowKeys = new Set<string>();
9218
+ let seenImplicitRowKeyBytes = 0;
8738
9219
  let processedCount = 0;
8739
9220
  let successfulCount = 0;
8740
9221
  let failedCount = 0;
@@ -8825,13 +9306,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8825
9306
  await flushChunk();
8826
9307
  };
8827
9308
 
8828
- const pipelinePages = !this.canUsePureJsMapFastPath(
8829
- input as MapFieldDefinition<Record<string, unknown>>,
8830
- );
8831
- const pageBudgetBytes = mapMemoryLimits.materializedBudgetBytes;
8832
- const persistedRowIdentity = (row: Record<string, unknown>, index = 0) =>
8833
- resolveMapRowOutcomeKey(row) ?? rowIdentity(row, index);
8834
-
9309
+ // The fed pool retains every appended row in executionRows. A staged
9310
+ // array must release each page after settlement to honor its source
9311
+ // reservation, including when a resolver invokes provider tools.
9312
+ const pipelinePages =
9313
+ !sealedArrayInput &&
9314
+ !this.canUsePureJsMapFastPath(
9315
+ input as MapFieldDefinition<Record<string, unknown>>,
9316
+ );
8835
9317
  type PreparedPage = {
8836
9318
  rows: Record<string, unknown>[];
8837
9319
  rowKeys: string[];
@@ -8877,7 +9359,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8877
9359
  }),
8878
9360
  };
8879
9361
  });
8880
- const mapStartResult = await this.#options.onMapStart!(
9362
+ const admission = await admitMapRows(
8881
9363
  pageStartRows,
8882
9364
  resolvedTableNamespace,
8883
9365
  {
@@ -8891,7 +9373,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8891
9373
  ? {
8892
9374
  includePreviousCells: true,
8893
9375
  previousCellsBudgetBytes: Math.floor(
8894
- mapMemoryLimits.materializedBudgetBytes /
9376
+ pageBudgetBytes /
8895
9377
  NODE_RUNTIME_MAP_MATERIALIZED_MEMORY_MULTIPLIER,
8896
9378
  ),
8897
9379
  }
@@ -8900,37 +9382,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8900
9382
  mode: options?.mode,
8901
9383
  columnTypes: unchangedColumnTypes,
8902
9384
  },
9385
+ pageBudgetBytes,
8903
9386
  );
8904
- resolvedTableNamespace = normalizeTableNamespace(
8905
- mapStartResult.tableNamespace,
8906
- );
8907
- const previousCellsByKey =
8908
- this.currentRuntimeContract.datasetRowInput === 'current_run'
8909
- ? assertDatasetAdmissionHistory(
8910
- mapStartResult,
8911
- Math.floor(
8912
- mapMemoryLimits.materializedBudgetBytes /
8913
- NODE_RUNTIME_MAP_MATERIALIZED_MEMORY_MULTIPLIER,
8914
- ),
8915
- )
8916
- : undefined;
8917
-
8918
- const pendingRowsByKey = new Map<string, Record<string, unknown>>();
8919
- for (
8920
- let index = 0;
8921
- index < mapStartResult.pendingRows.length;
8922
- index += 1
8923
- ) {
8924
- const row = mapStartResult.pendingRows[index]!;
8925
- const rowKey = persistedRowIdentity(row, page.offset + index);
8926
- if (rowKey) pendingRowsByKey.set(rowKey, row);
8927
- }
8928
- const completedRowsByKey = new Map<string, Record<string, unknown>>();
8929
- for (const row of mapStartResult.completedRows ?? []) {
8930
- const rowKey = persistedRowIdentity(row);
8931
- if (!rowKey) continue;
8932
- completedRowsByKey.set(rowKey, row);
8933
- }
9387
+ resolvedTableNamespace = admission.tableNamespace;
9388
+ const { pendingRowsByKey, completedRowsByKey, previousCellsByKey } =
9389
+ admission;
8934
9390
 
8935
9391
  const seededItems = pageRawItems.map((row, index) => {
8936
9392
  const originalIndex = page.offset + index;
@@ -8964,14 +9420,64 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8964
9420
  for (const { row, index } of admittedSeededItems) {
8965
9421
  if (explicitKeyResolver && !useSourceRowIdentity) {
8966
9422
  const explicitKey = explicitKeyResolver(row, index);
8967
- if (seenExplicitKeys.has(explicitKey)) {
9423
+ const dedupeToken = sealedArrayInput
9424
+ ? sha256Hex(explicitKey)
9425
+ : explicitKey;
9426
+ if (seenExplicitKeys.has(dedupeToken)) {
8968
9427
  duplicateReuseCount += 1;
8969
9428
  processedCount += 1;
8970
9429
  continue;
8971
9430
  }
8972
- seenExplicitKeys.add(explicitKey);
9431
+ if (sealedArrayInput) {
9432
+ seenExplicitKeyBytes +=
9433
+ 96 + Buffer.byteLength(dedupeToken, 'utf8');
9434
+ if (seenExplicitKeyBytes > keyIndexBudgetBytes) {
9435
+ throw new RuntimeMapMemoryLimitError({
9436
+ mapName: normalizedMapNamespace,
9437
+ phase: 'materialize',
9438
+ estimate: {
9439
+ ...(arraySourceMemoryEstimate ??
9440
+ estimateRuntimeMapRowsMemory([])),
9441
+ estimatedResidentBytes:
9442
+ (arraySourceMemoryEstimate?.estimatedResidentBytes ?? 0) +
9443
+ seenExplicitKeyBytes +
9444
+ pageBudgetBytes * 4,
9445
+ },
9446
+ budgetBytes: mapMemoryLimits.materializedBudgetBytes,
9447
+ });
9448
+ }
9449
+ }
9450
+ seenExplicitKeys.add(dedupeToken);
8973
9451
  }
8974
9452
  const rowKey = rowIdentity(row, index);
9453
+ if (
9454
+ sealedArrayInput &&
9455
+ !explicitKeyResolver &&
9456
+ !useSourceRowIdentity
9457
+ ) {
9458
+ if (seenImplicitRowKeys.has(rowKey)) {
9459
+ duplicateReuseCount += 1;
9460
+ processedCount += 1;
9461
+ continue;
9462
+ }
9463
+ seenImplicitRowKeyBytes += 96 + Buffer.byteLength(rowKey, 'utf8');
9464
+ if (seenImplicitRowKeyBytes > keyIndexBudgetBytes) {
9465
+ throw new RuntimeMapMemoryLimitError({
9466
+ mapName: normalizedMapNamespace,
9467
+ phase: 'materialize',
9468
+ estimate: {
9469
+ ...(arraySourceMemoryEstimate ??
9470
+ estimateRuntimeMapRowsMemory([])),
9471
+ estimatedResidentBytes:
9472
+ (arraySourceMemoryEstimate?.estimatedResidentBytes ?? 0) +
9473
+ seenImplicitRowKeyBytes +
9474
+ pageBudgetBytes * 4,
9475
+ },
9476
+ budgetBytes: mapMemoryLimits.materializedBudgetBytes,
9477
+ });
9478
+ }
9479
+ seenImplicitRowKeys.add(rowKey);
9480
+ }
8975
9481
  if (!rowsToExecuteByKey.has(rowKey)) {
8976
9482
  rowsToExecuteByKey.set(rowKey, { row, originalIndex: index });
8977
9483
  } else {
@@ -9054,6 +9560,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9054
9560
  logicalNamespace: mapScope.logicalNamespace,
9055
9561
  artifactTableNamespace: resolvedTableNamespace,
9056
9562
  status: 'running',
9563
+ arrayInput: sealedArrayInput
9564
+ ? { hasSourceIdentities: sealedArrayInput.hasSourceIdentities }
9565
+ : undefined,
9057
9566
  totalRows: totalInputCount,
9058
9567
  completedRowKeys: [],
9059
9568
  pendingRowKeys: execution.rowFeed ? [] : [...execution.rowKeys],
@@ -9094,8 +9603,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9094
9603
  progressSkippedOffset: () => duplicateReuseCount,
9095
9604
  progressTotalRows: () => totalInputCount,
9096
9605
  rowFeed: execution.rowFeed,
9097
- retainedRowsMemoryBudgetBytes:
9098
- mapMemoryLimits.materializedBudgetBytes,
9606
+ retainedRowsMemoryBudgetBytes: pageBudgetBytes,
9099
9607
  incrementalPersistence,
9100
9608
  releasePersistedRowsOnMemoryPressure: true,
9101
9609
  },
@@ -9181,9 +9689,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9181
9689
  (observedResidentBytes / observedRowCount) *
9182
9690
  Math.max(observedRowCount, totalInputCount - failedCount),
9183
9691
  );
9184
- if (
9185
- projectedResidentBytes > mapMemoryLimits.materializedBudgetBytes
9186
- ) {
9692
+ if (projectedResidentBytes > pageBudgetBytes) {
9187
9693
  // All-or-nothing cache: retaining a prefix that cannot fit once
9188
9694
  // the known input finishes gives authored code no complete fast
9189
9695
  // path. More importantly, that prefix would overlap the next
@@ -9192,7 +9698,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9192
9698
  // rows observed so far and move to the already-durable sheet
9193
9699
  // early instead of waiting for resident memory to reach a cliff.
9194
9700
  disableImmediateMaterializedCache(
9195
- `projected ${projectedResidentBytes} bytes exceeds ${mapMemoryLimits.materializedBudgetBytes}-byte budget`,
9701
+ `projected ${projectedResidentBytes} bytes exceeds ${pageBudgetBytes}-byte result cache budget`,
9196
9702
  );
9197
9703
  } else {
9198
9704
  immediateMaterializedRows.push(materializedRow);
@@ -9216,13 +9722,83 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9216
9722
  processedCount += execution.rows.length;
9217
9723
  };
9218
9724
 
9219
- const sourcePages = iteratePlayDatasetInputPages(items, {
9220
- maxPageBytes: pageBudgetBytes,
9221
- estimateRowBytes: (row) =>
9222
- runtimeMapJsonByteLength(row) *
9223
- NODE_RUNTIME_MAP_MATERIALIZED_MEMORY_MULTIPLIER +
9224
- NODE_RUNTIME_MAP_ROW_OVERHEAD_BYTES,
9225
- });
9725
+ const sourcePages = iteratePlayDatasetInputPages<T>(
9726
+ sealedArrayInput
9727
+ ? (iterateSealedRuntimeArrayInput(
9728
+ arrayInputStore!,
9729
+ arrayInputScope,
9730
+ sealedArrayInput,
9731
+ ) as AsyncIterable<T>)
9732
+ : items,
9733
+ {
9734
+ maxPageBytes: pageBudgetBytes,
9735
+ estimateRowBytes: (item, index, sourceIdentity) => {
9736
+ // The legacy single callback's final guard prices raw rows. Keep
9737
+ // its existing cuts; only current-run seeding or paged retention
9738
+ // introduces the execution/admission shapes accounted below.
9739
+ if (
9740
+ this.currentRuntimeContract.datasetRowInput !== 'current_run' &&
9741
+ !this.#options.onMapStartPages
9742
+ ) {
9743
+ return (
9744
+ runtimeMapJsonByteLength(item) *
9745
+ NODE_RUNTIME_MAP_MATERIALIZED_MEMORY_MULTIPLIER +
9746
+ NODE_RUNTIME_MAP_ROW_OVERHEAD_BYTES
9747
+ );
9748
+ }
9749
+ const row = this.toOutputRow(item as Record<string, unknown>);
9750
+ if (sourceIdentity) {
9751
+ setPlayDatasetSourceRowIdentity(row, sourceIdentity);
9752
+ }
9753
+ // Estimate the execution shape before any admission side effect.
9754
+ // Reserve a derived key's maximum namespace without invoking an
9755
+ // authored key resolver a second time. History never changes page
9756
+ // cuts; its returned values must fit this same fixed page budget.
9757
+ const stamped = {
9758
+ ...toSerializableCsvAliasedRow(row),
9759
+ ...(sourceIdentity
9760
+ ? { __deeplineSourceRow: sourceIdentity }
9761
+ : {}),
9762
+ ...mapRowOutcomeRuntimeFields({
9763
+ key:
9764
+ resolveMapRowOutcomeKey(row) ??
9765
+ 'x'.repeat(MAP_KEY_NAMESPACE_MAX_LENGTH + 1 + 64),
9766
+ inputIndex: index,
9767
+ }),
9768
+ };
9769
+ const seeded = this.datasetRowInputs.seed(
9770
+ row,
9771
+ stamped,
9772
+ this.currentRuntimeContract.datasetRowInput,
9773
+ );
9774
+ const executionEstimate = estimateRuntimeMapRowsMemory([
9775
+ this.datasetRowInputs.memoryValue(seeded),
9776
+ ]);
9777
+ // Paged admission retains the stamped row and an independent
9778
+ // history-map entry before seeding. Cover both existing trackers.
9779
+ const admissionEstimate = estimateRuntimeMapRowsMemory([
9780
+ stamped,
9781
+ ...(this.currentRuntimeContract.datasetRowInput === 'current_run'
9782
+ ? [{ previousCells: {} }]
9783
+ : []),
9784
+ ]);
9785
+ const estimate =
9786
+ executionEstimate.estimatedResidentBytes >
9787
+ admissionEstimate.estimatedResidentBytes
9788
+ ? executionEstimate
9789
+ : admissionEstimate;
9790
+ if (estimate.estimatedResidentBytes > pageBudgetBytes) {
9791
+ throw new RuntimeMapMemoryLimitError({
9792
+ mapName: normalizedMapNamespace,
9793
+ phase: 'materialize',
9794
+ estimate,
9795
+ budgetBytes: pageBudgetBytes,
9796
+ });
9797
+ }
9798
+ return estimate.estimatedResidentBytes;
9799
+ },
9800
+ },
9801
+ );
9226
9802
 
9227
9803
  if (pipelinePages) {
9228
9804
  // One worker pool for the whole source. Once page N's last row has
@@ -9327,6 +9903,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9327
9903
  logicalNamespace: mapScope.logicalNamespace,
9328
9904
  artifactTableNamespace: resolvedTableNamespace,
9329
9905
  status: 'failed',
9906
+ arrayInput: sealedArrayInput
9907
+ ? { hasSourceIdentities: sealedArrayInput.hasSourceIdentities }
9908
+ : undefined,
9330
9909
  totalRows: totalInputCount,
9331
9910
  completedRowKeys: [],
9332
9911
  pendingRowKeys: [],
@@ -9370,6 +9949,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9370
9949
  logicalNamespace: mapScope.logicalNamespace,
9371
9950
  artifactTableNamespace: resolvedTableNamespace,
9372
9951
  status: 'completed',
9952
+ arrayInput: sealedArrayInput
9953
+ ? { hasSourceIdentities: sealedArrayInput.hasSourceIdentities }
9954
+ : undefined,
9373
9955
  totalRows: totalInputCount,
9374
9956
  completedRowKeys: [],
9375
9957
  pendingRowKeys: [],
@@ -9620,12 +10202,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9620
10202
  // counts, sheet writes, resumable keys) consistent — the resolver derives
9621
10203
  // keys from row content, so dropped rows are excluded everywhere with no
9622
10204
  // index mismatch. Replaces the prior throw-on-duplicate behavior.
10205
+ const materializedKeyResolver = explicitKeyResolver;
9623
10206
  const dedupedMaterialized =
9624
- explicitKeyResolver && !useSourceRowIdentity
10207
+ materializedKeyResolver && !useSourceRowIdentity
9625
10208
  ? dedupeExplicitMapKeyRows({
9626
10209
  rows: rawMaterializedItems,
9627
10210
  resolver: (item, index) =>
9628
- explicitKeyResolver(
10211
+ materializedKeyResolver(
9629
10212
  this.toOutputRow(item as Record<string, unknown>),
9630
10213
  originalInputIndexByItem.get(item) ?? index,
9631
10214
  ),
@@ -9796,7 +10379,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9796
10379
  (item, index) => originalInputIndexByItem.get(item) ?? index,
9797
10380
  );
9798
10381
  const mapStartSeedRowsByKey = new Map<string, Record<string, unknown>>();
9799
- if (this.#options.onMapStart) {
10382
+ if (this.#options.onMapStart || this.#options.onMapStartPages) {
9800
10383
  // ALWAYS stamp the canonical seed row key — including no-explicit-key
9801
10384
  // datasets — using seedRowIdentity (full-input-row hash, matching the
9802
10385
  // server's own derivation). Without the stamp, seed and completion keys
@@ -9832,7 +10415,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9832
10415
  }),
9833
10416
  };
9834
10417
  });
9835
- const mapStartResult = await this.#options.onMapStart(
10418
+ const admission = await admitMapRows(
9836
10419
  mapStartRows,
9837
10420
  resolvedTableNamespace,
9838
10421
  {
@@ -9855,35 +10438,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9855
10438
  columnTypes: unchangedColumnTypes,
9856
10439
  },
9857
10440
  );
9858
- resolvedTableNamespace = normalizeTableNamespace(
9859
- mapStartResult.tableNamespace,
9860
- );
9861
- const previousCellsByKey =
9862
- this.currentRuntimeContract.datasetRowInput === 'current_run'
9863
- ? assertDatasetAdmissionHistory(
9864
- mapStartResult,
9865
- Math.floor(
9866
- mapMemoryLimits.materializedBudgetBytes /
9867
- NODE_RUNTIME_MAP_MATERIALIZED_MEMORY_MULTIPLIER,
9868
- ),
9869
- )
9870
- : undefined;
9871
- const persistedRowIdentity = (row: Record<string, unknown>, index = 0) =>
9872
- resolveMapRowOutcomeKey(row) ?? rowIdentity(row, index);
9873
- const pendingRowsByKey = new Map<string, Record<string, unknown>>();
9874
- for (
9875
- let index = 0;
9876
- index < mapStartResult.pendingRows.length;
9877
- index += 1
9878
- ) {
9879
- const row = mapStartResult.pendingRows[index]!;
9880
- const rowKey = persistedRowIdentity(row, index);
9881
- if (rowKey) pendingRowsByKey.set(rowKey, row);
9882
- }
9883
- for (const row of mapStartResult.completedRows ?? []) {
9884
- const rowKey = persistedRowIdentity(row);
9885
- if (!rowKey) continue;
9886
- mapStartSeedRowsByKey.set(rowKey, row);
10441
+ resolvedTableNamespace = admission.tableNamespace;
10442
+ const { pendingRowsByKey, previousCellsByKey } = admission;
10443
+ for (const [key, row] of admission.completedRowsByKey) {
10444
+ mapStartSeedRowsByKey.set(key, row);
9887
10445
  }
9888
10446
  const seededItems = materializedItems
9889
10447
  .map((item, index) => ({
@@ -10815,6 +11373,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10815
11373
  : Math.min(items.length, requestedRowConcurrency);
10816
11374
  const heapAdmission = createRuntimeMapHeapAdmission({
10817
11375
  maxConcurrency: rowConcurrencyLimit,
11376
+ // The resolver above already rejects invalid supplied run maxima.
11377
+ // Share its authored-width predicate so invalid internal widths cannot
11378
+ // turn an automatic map into a requested missing-probe bypass.
11379
+ concurrencySource:
11380
+ hasExplicitRowConcurrency(this.#options.maxConcurrentRows) ||
11381
+ hasExplicitRowConcurrency(runtimeOptions?.concurrency)
11382
+ ? 'requested'
11383
+ : 'automatic',
10818
11384
  memoryLedger: this.memoryLedger,
10819
11385
  ...(this.#options.runtimeMapHeapProbe
10820
11386
  ? { heapProbe: this.#options.runtimeMapHeapProbe }
@@ -10833,7 +11399,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10833
11399
  // composes the existing capacity boundaries and returns them in reverse at
10834
11400
  // the row's durable/in-memory handoff below.
10835
11401
  const mapRowFlow = createRuntimeFlow<{
10836
- baseRow: Record<string, unknown>;
11402
+ inputRow: T | Record<string, unknown>;
10837
11403
  }>({
10838
11404
  stages: [
10839
11405
  {
@@ -10842,10 +11408,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10842
11408
  },
10843
11409
  {
10844
11410
  name: 'run-row',
10845
- acquire: async ({ baseRow }) =>
11411
+ acquire: async ({ inputRow }) =>
10846
11412
  await this.resourceGovernor.acquireRow({
11413
+ // The source is already resident in the collected baseline or
11414
+ // accounted source page. Preserve this measurement as telemetry;
11415
+ // reserving it again would double-charge its existing bytes.
10847
11416
  estimatedBytes: runtimeMapJsonByteLength(
10848
- this.datasetRowInputs.memoryValue(baseRow),
11417
+ this.datasetRowInputs.memoryValue(this.toOutputRow(inputRow)),
10849
11418
  ),
10850
11419
  }),
10851
11420
  },
@@ -11439,48 +12008,60 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11439
12008
  const waitingRow = providerRetry
11440
12009
  ? waitingBaseRows.get(idx)
11441
12010
  : undefined;
11442
- const baseRow = providerRetry
11443
- ? waitingRow?.baseRow
11444
- : this.toOutputRow(items[idx]);
11445
- if (!baseRow) {
12011
+ if (providerRetry && !waitingRow?.baseRow) {
11446
12012
  throw new Error(
11447
12013
  `Provider retry row ${idx} lost its resumable input.`,
11448
12014
  );
11449
12015
  }
11450
- historyRow = baseRow;
11451
- if (this.currentRuntimeContract.datasetRowInput === 'current_run') {
11452
- activeRowHistories.set(idx, baseRow);
11453
- this.datasetRowInputs.retain(baseRow, this.memoryLedger);
11454
- }
11455
- if (providerRetry) waitingBaseRows.delete(idx);
11456
- else releaseDispatchedInputRow?.(idx);
11457
- const callIndexes =
11458
- waitingRow?.callIndexes ?? new RowReplayCallIndexes();
11459
- callIndexes.resetAttempt();
11460
- const retryAfterRelease = {
11461
- pending: null as ProviderRowRetry | null,
11462
- callIndexes,
11463
- };
11464
- settledResults[idx] = await mapRowFlow.run({ baseRow }, async () => {
12016
+ const inputRow = waitingRow?.baseRow ?? items[idx];
12017
+ const rowAttempt: { retry?: ProviderRowRetry } = {};
12018
+ settledResults[idx] = await mapRowFlow.run({ inputRow }, async () => {
11465
12019
  this.assertRowAdmissionOpen();
12020
+ // Heap and run-row admission precede retaining per-row history or
12021
+ // replay state. Workers waiting for capacity retain only their
12022
+ // index and the already-resident input reference.
12023
+ const baseRow = providerRetry
12024
+ ? waitingRow?.baseRow
12025
+ : this.toOutputRow(inputRow);
12026
+ if (!baseRow) {
12027
+ throw new Error(
12028
+ `Provider retry row ${idx} lost its resumable input.`,
12029
+ );
12030
+ }
12031
+ historyRow = baseRow;
12032
+ if (this.currentRuntimeContract.datasetRowInput === 'current_run') {
12033
+ activeRowHistories.set(idx, baseRow);
12034
+ this.datasetRowInputs.retain(baseRow, this.memoryLedger);
12035
+ }
12036
+ if (providerRetry) waitingBaseRows.delete(idx);
12037
+ else releaseDispatchedInputRow?.(idx);
12038
+ const callIndexes =
12039
+ waitingRow?.callIndexes ?? new RowReplayCallIndexes();
12040
+ callIndexes.resetAttempt();
12041
+ const retryAfterRelease = {
12042
+ pending: null as ProviderRowRetry | null,
12043
+ callIndexes,
12044
+ };
11466
12045
  mapProgress.resolverStarted({ retry: Boolean(providerRetry) });
11467
12046
  try {
11468
- return await runRow(idx, baseRow, retryAfterRelease);
12047
+ const result = await runRow(idx, baseRow, retryAfterRelease);
12048
+ rowAttempt.retry = retryAfterRelease.pending ?? undefined;
12049
+ return result;
11469
12050
  } finally {
11470
12051
  mapProgress.resolverFinished();
11471
12052
  }
11472
12053
  });
11473
12054
  // Publish only after both flow leases have returned. Another worker
11474
12055
  // must never re-admit this index while the old attempt is unwinding.
11475
- if (retryAfterRelease.pending) {
11476
- if (retryAfterRelease.pending.residentWaitBudgetExhausted) {
11477
- parkedProviderRows.add(retryAfterRelease.pending);
12056
+ if (rowAttempt.retry) {
12057
+ if (rowAttempt.retry.residentWaitBudgetExhausted) {
12058
+ parkedProviderRows.add(rowAttempt.retry);
11478
12059
  } else {
11479
- providerRowRetries.add(retryAfterRelease.pending);
12060
+ providerRowRetries.add(rowAttempt.retry);
11480
12061
  }
11481
12062
  rowFeed?.wakeWorkersAt(nextProviderRetry(true)!.readyAtMs);
11482
12063
  } else if (settledResults[idx] !== WAITING_ROW) {
11483
- this.datasetRowInputs.release(baseRow);
12064
+ if (historyRow) this.datasetRowInputs.release(historyRow);
11484
12065
  providerIdleWaitStartedAt = null;
11485
12066
  }
11486
12067
  if (mapProgress.activeResolvers === 0) retryWakeFeed.wakeWorkers();
@@ -11501,6 +12082,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11501
12082
  }
11502
12083
  });
11503
12084
 
12085
+ const unregisterExternalPressure =
12086
+ this.currentExecutionGovernor.registerExternalExecutionPressure?.({
12087
+ read: () => heapAdmission.readExternalExecutionPressure(),
12088
+ subscribe: (changed) =>
12089
+ heapAdmission.subscribeExternalExecutionPressure(changed),
12090
+ }) ?? (() => {});
11504
12091
  let settled: PromiseSettledResult<void>[];
11505
12092
  try {
11506
12093
  // Drain queued work but exit early if a suspension error occurs.
@@ -11580,6 +12167,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11580
12167
  settled = await Promise.allSettled(workers);
11581
12168
  }
11582
12169
  } finally {
12170
+ unregisterExternalPressure();
11583
12171
  clearInterval(mapActivityTimer);
11584
12172
  retryWakeFeed.wakeWorkers();
11585
12173
  publishMapActivity();
@@ -16242,7 +16830,23 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
16242
16830
 
16243
16831
  /** Aggregate waits and residence from this logical run for terminal diagnostics. */
16244
16832
  getRuntimeResourceSnapshot(): RuntimeResourceSnapshot {
16245
- return this.resourceGovernor.snapshot();
16833
+ const body = this.providerResponseBodyGate.snapshot();
16834
+ return {
16835
+ ...this.resourceGovernor.snapshot(),
16836
+ memoryAdmission: {
16837
+ toolResponseBody: {
16838
+ turns: body.turns,
16839
+ measuredTurns: body.measuredTurns,
16840
+ decodedUtf8BytesTotal: body.bytesMeasured,
16841
+ decodedUtf8BytesMax: body.bytesMeasuredMax,
16842
+ permitBytesPeak: body.bytesHeldPeak,
16843
+ queuedTurns: body.queuedTurns,
16844
+ budgetRefusals: body.holds,
16845
+ progressAdmissions: body.progressAdmissions,
16846
+ unpricedAdmissions: body.unpricedAdmissions,
16847
+ },
16848
+ },
16849
+ };
16246
16850
  }
16247
16851
 
16248
16852
  /** Attempt-local counts without serializing the diagnostic resource map. */
@@ -16728,150 +17332,73 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
16728
17332
  const { toolId, requests } = input;
16729
17333
  if (requests.length === 0) return [];
16730
17334
 
17335
+ // Admit receipt and presentation identities once. Provider invocation
17336
+ // identity remains the execution Adapter's existing request contract.
17337
+ // Canonical cache force differs from authored request.force on replay.
17338
+ const members = requests.map((request) => ({
17339
+ memberId: request.callId,
17340
+ ...this.mappedToolResultReceiptPolicy(request),
17341
+ cacheKey: request.cacheKey,
17342
+ receiptKey: request.receiptKey!,
17343
+ forceFailedRefresh: request.forceFailedRefresh === true,
17344
+ waitMaxAttempts: resolveRuntimeToolReceiptWaitMaxAttempts(
17345
+ typeof request.receiptWaitMs === 'number'
17346
+ ? { max_wait_ms: request.receiptWaitMs }
17347
+ : request.input,
17348
+ ),
17349
+ request,
17350
+ }));
16731
17351
  const requestForReceipt = new Map<string, ToolCallRequest>();
16732
17352
  const requestsForReceipt = new Map<string, ToolCallRequest[]>();
16733
17353
  const requestByCallId = new Map<string, ToolCallRequest>();
16734
- for (const request of requests) {
16735
- const receiptKey =
16736
- this.mappedToolResultReceiptPolicy(request).resultReceiptKey;
16737
- requestForReceipt.set(receiptKey, request);
16738
- const shared = requestsForReceipt.get(receiptKey);
16739
- if (shared) shared.push(request);
16740
- else requestsForReceipt.set(receiptKey, [request]);
16741
- requestByCallId.set(request.callId, request);
16742
- }
16743
-
16744
- const store = this.createRuntimeStepToolCallReceiptCohortStore({
16745
- toolId,
16746
- requestForReceipt,
16747
- requestsForReceipt,
16748
- });
16749
-
16750
- const cohort = await createToolCallReceiptCohortJob({
16751
- store,
16752
- timings: this.toolCallReceiptCohortTimings(requestByCallId),
16753
- }).acquire({
16754
- owner: {
16755
- runId: this.currentReceiptOwnerRunId,
16756
- attempt: this.currentRunAttempt,
16757
- },
16758
- members: requests.map((request) => ({
16759
- memberId: request.callId,
16760
- ...this.mappedToolResultReceiptPolicy(request),
16761
- forceFailedRefresh: request.forceFailedRefresh === true,
16762
- waitMaxAttempts: resolveRuntimeToolReceiptWaitMaxAttempts(
16763
- typeof request.receiptWaitMs === 'number'
16764
- ? { max_wait_ms: request.receiptWaitMs }
16765
- : request.input,
16766
- ),
16767
- })),
16768
- });
16769
-
16770
- const successful: ToolCallRequest[] = [];
16771
- const resolve = async (
16772
- request: ToolCallRequest,
16773
- result: ToolExecuteResult,
16774
- source: 'cache' | 'owner',
16775
- ): Promise<void> => {
16776
- const receiptKey = request.receiptKey!;
16777
- const finalResult = cloneToolExecuteResultWithExecution(result, {
16778
- ...toolExecutionMetadataForOutcome({
16779
- kind: source === 'owner' ? 'live' : source,
16780
- cacheKey: request.cacheKey,
16781
- receiptKey,
16782
- ...(source === 'owner' ? {} : { attachedToReceiptKey: receiptKey }),
16783
- }),
16784
- resultReceiptKey:
16785
- this.mappedToolResultReceiptPolicy(request).resultReceiptKey,
16786
- });
16787
- this.cacheToolResult(toolId, request.cacheKey, finalResult);
16788
- const resolver = this.toolCallResolvers.get(request.callId);
16789
- if (resolver) {
16790
- resolver.resolve(finalResult);
16791
- this.toolCallResolvers.delete(request.callId);
16792
- }
16793
- this.emitScopedFieldMetaUpdate({
16794
- rowId: request.rowId,
16795
- key: request.rowKey ?? null,
16796
- tableNamespace: request.tableNamespace ?? null,
16797
- fieldName: request.fieldName,
16798
- status: source === 'owner' ? 'running' : 'cached',
16799
- rowStatus: 'running',
16800
- stage: toolId,
16801
- provider: null,
16802
- error: null,
16803
- ...(source === 'owner' ? {} : { reused: true }),
16804
- dataPatch: {},
16805
- });
16806
- successful.push(request);
16807
- };
16808
- const reject = async (request: ToolCallRequest, error: unknown) => {
16809
- await this.rejectToolCall(toolId, request, error, {
16810
- // Failures are never cache entries, including failed local misses.
16811
- persistReceiptFailure: false,
16812
- });
16813
- };
16814
-
16815
- const cachedResults: Promise<void>[] = [];
16816
- const owned: Array<{
16817
- request: ToolCallRequest;
16818
- receipts: ToolResultReceipts;
16819
- }> = [];
16820
- for (const request of requests) {
16821
- const member = cohort.member(request.callId);
16822
- if (member.kind === 'completed') {
16823
- cachedResults.push(resolve(request, member.result, member.source));
16824
- } else if (member.kind === 'owned') {
16825
- owned.push({ request, receipts: member.receipts });
16826
- } else {
16827
- throw new Error(
16828
- 'A completed-result cache cannot own a pending invocation.',
16829
- );
16830
- }
16831
- }
16832
-
16833
- const width = Math.min(
16834
- this.governor.policy.concurrency.toolCalls,
16835
- Math.max(
16836
- 1,
16837
- await this.resourceGovernor.suggestedToolParallelism(
17354
+ for (const member of members) {
17355
+ requestForReceipt.set(member.resultReceiptKey, member.request);
17356
+ const shared = requestsForReceipt.get(member.resultReceiptKey);
17357
+ if (shared) shared.push(member.request);
17358
+ else requestsForReceipt.set(member.resultReceiptKey, [member.request]);
17359
+ requestByCallId.set(member.memberId, member.request);
17360
+ }
17361
+ const calls = createMappedToolCallJob<ToolCallRequest>({
17362
+ receiptCohort: createToolCallReceiptCohortJob({
17363
+ store: this.createRuntimeStepToolCallReceiptCohortStore({
16838
17364
  toolId,
16839
- this.governor.policy.concurrency.toolCalls,
16840
- ),
16841
- ),
16842
- );
16843
- await dispatchBoundedSettled(
16844
- owned,
16845
- width,
16846
- async ({ request, receipts }) => {
16847
- // Circuit breaker: do not dispatch this provider call once a
16848
- // persistence failure has occurred in this run. Rejected through the
16849
- // same path as a provider failure — `dispatchBoundedSettled` only
16850
- // collects a thrown error, so throwing past `reject` here would
16851
- // leave this row's tool-call resolver pending for the life of the
16852
- // map.
16853
- if (this.persistenceLatch.tripped) {
17365
+ requestForReceipt,
17366
+ requestsForReceipt,
17367
+ }),
17368
+ timings: this.toolCallReceiptCohortTimings(requestByCallId),
17369
+ }),
17370
+ admission: {
17371
+ parallelism: async () =>
17372
+ Math.min(
17373
+ this.governor.policy.concurrency.toolCalls,
17374
+ Math.max(
17375
+ 1,
17376
+ await this.resourceGovernor.suggestedToolParallelism(
17377
+ toolId,
17378
+ this.governor.policy.concurrency.toolCalls,
17379
+ ),
17380
+ ),
17381
+ ),
17382
+ assertDispatchAllowed: () => {
17383
+ if (!this.persistenceLatch.tripped) return;
16854
17384
  this.persistenceLatch.preventedCallCount += 1;
16855
- await reject(
16856
- request,
16857
- new RuntimePersistenceCircuitOpenError(this.persistenceLatch),
16858
- );
16859
- return;
16860
- }
16861
- let releaseToolSlot: () => void = () => undefined;
16862
- try {
17385
+ throw new RuntimePersistenceCircuitOpenError(this.persistenceLatch);
17386
+ },
17387
+ },
17388
+ execution: {
17389
+ call: async ({ member, receipts, retainToolSlot }) => {
17390
+ const request = member.request;
16863
17391
  const providerOperationKey = this.providerInvocationKeyForToolCall({
16864
17392
  operationKey:
16865
17393
  request.providerIdempotencyKeyBase ?? request.receiptKey!,
16866
17394
  logicalCallId: request.logicalCallId,
16867
17395
  force: request.force,
16868
17396
  });
16869
- const result = await this.executeMappedToolCallThroughModule({
17397
+ return this.executeMappedToolCallThroughModule({
16870
17398
  request,
16871
17399
  dispatchToolId: toolId,
16872
17400
  dispatchPayload: request.input,
16873
- resultReceiptKey:
16874
- this.mappedToolResultReceiptPolicy(request).resultReceiptKey,
17401
+ resultReceiptKey: member.resultReceiptKey,
16875
17402
  providerOperationKey,
16876
17403
  resultReceipts: receipts,
16877
17404
  apiOptions: {
@@ -16910,22 +17437,52 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
16910
17437
  typeof request.timeoutMs === 'number' && request.timeoutMs > 0
16911
17438
  ? request.timeoutMs
16912
17439
  : undefined,
16913
- retainToolSlot: (release) => {
16914
- releaseToolSlot = release;
16915
- },
17440
+ retainToolSlot,
16916
17441
  },
16917
17442
  });
16918
- await resolve(request, result, 'owner');
16919
- } catch (error) {
16920
- await reject(request, error);
16921
- } finally {
16922
- releaseToolSlot();
16923
- }
17443
+ },
17444
+ },
17445
+ delivery: {
17446
+ complete: async ({ member, result, source }) => {
17447
+ const request = member.request;
17448
+ this.cacheToolResult(toolId, member.cacheKey, result);
17449
+ const resolver = this.toolCallResolvers.get(member.memberId);
17450
+ if (resolver) {
17451
+ resolver.resolve(result);
17452
+ this.toolCallResolvers.delete(member.memberId);
17453
+ }
17454
+ this.emitScopedFieldMetaUpdate({
17455
+ rowId: request.rowId,
17456
+ key: request.rowKey ?? null,
17457
+ tableNamespace: request.tableNamespace ?? null,
17458
+ fieldName: request.fieldName,
17459
+ status: source === 'owner' ? 'running' : 'cached',
17460
+ rowStatus: 'running',
17461
+ stage: toolId,
17462
+ provider: null,
17463
+ error: null,
17464
+ ...(source === 'owner' ? {} : { reused: true }),
17465
+ dataPatch: {},
17466
+ });
17467
+ },
17468
+ fail: async ({ member, error }) => {
17469
+ await this.rejectToolCall(toolId, member.request, error, {
17470
+ // A failure never replaces an immutable successful cache entry.
17471
+ persistReceiptFailure: false,
17472
+ });
17473
+ },
17474
+ },
17475
+ });
17476
+ const { successfulMemberIds } = await calls.call({
17477
+ owner: {
17478
+ runId: this.currentReceiptOwnerRunId,
17479
+ attempt: this.currentRunAttempt,
16924
17480
  },
17481
+ members,
17482
+ });
17483
+ return successfulMemberIds.map(
17484
+ (memberId) => requestByCallId.get(memberId)!,
16925
17485
  );
16926
-
16927
- await Promise.all(cachedResults);
16928
- return successful;
16929
17486
  }
16930
17487
 
16931
17488
  /**
@@ -19148,8 +19705,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19148
19705
  // the current checkpoint cursor index belongs to a newer batch.
19149
19706
  const providerResidentWaitKey = durableCallReceiptKey;
19150
19707
  let transportAttempt = 0;
19708
+ let connectFailures = 0;
19709
+ let firstConnectFailureAtMs: number | null = null;
19151
19710
  let invocationAttempt =
19152
19711
  savedProviderRetryCursor?.invocationAttempt ?? 0;
19712
+ // The no-send claim covers the whole logical call, including
19713
+ // deliveries made before a checkpoint resume.
19714
+ let earlierDeliveryMayHaveArrived =
19715
+ savedProviderRetryCursor !== undefined || invocationAttempt > 0;
19153
19716
  clearProviderRetryCursor = () => {
19154
19717
  if (providerResidentWaitKey)
19155
19718
  this.exhaustedProviderResidentWaitCalls.delete(
@@ -19271,8 +19834,21 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19271
19834
  aborted: boolean;
19272
19835
  /** The ingress rejected the request before any provider dispatch. */
19273
19836
  knownNotDispatched?: boolean;
19837
+ /** No socket existed, so no byte of the request was written. */
19838
+ connectionNeverOpened?: boolean;
19274
19839
  }): Promise<void> => {
19275
- transportAttempt += 1;
19840
+ if (!input.connectionNeverOpened)
19841
+ earlierDeliveryMayHaveArrived = true;
19842
+ let connectRetryDelayMs: number | null = null;
19843
+ if (input.connectionNeverOpened) {
19844
+ connectFailures += 1;
19845
+ firstConnectFailureAtMs ??= Date.now();
19846
+ connectRetryDelayMs = toolExecuteConnectRetryDelayMs({
19847
+ failures: connectFailures,
19848
+ elapsedMs: Date.now() - firstConnectFailureAtMs,
19849
+ });
19850
+ }
19851
+ if (connectRetryDelayMs === null) transportAttempt += 1;
19276
19852
  const diagnostic = describeTransportError(input.error);
19277
19853
  this.runtimeLog(
19278
19854
  `[runtime.transport_failure] ${JSON.stringify({
@@ -19283,9 +19859,23 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19283
19859
  elapsed_ms: input.elapsedMs,
19284
19860
  request_id: input.requestId,
19285
19861
  aborted: input.aborted,
19862
+ connection_never_opened: input.connectionNeverOpened === true,
19286
19863
  error: diagnostic,
19287
19864
  })}`,
19288
19865
  );
19866
+ if (connectRetryDelayMs !== null) {
19867
+ // Nothing reached the gateway, so this is neither a provider
19868
+ // attempt nor a charged retry: the logical attempt stays as it was.
19869
+ span.setAttribute(
19870
+ 'plays.connect_retry_failures',
19871
+ connectFailures,
19872
+ );
19873
+ this.runtimeLog(
19874
+ `Tool ${toolId} could not open a connection for ${url}; this attempt sent no request; retrying after ${connectRetryDelayMs}ms: ${diagnostic.message ?? 'unknown transport error'}`,
19875
+ );
19876
+ await waitForToolRetry(connectRetryDelayMs, false);
19877
+ return;
19878
+ }
19289
19879
  if (
19290
19880
  transportAttempt < TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS &&
19291
19881
  (input.knownNotDispatched === true ||
@@ -19318,7 +19908,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19318
19908
  }
19319
19909
  throw createToolHttpError(
19320
19910
  toolErrorSchemaVersion,
19321
- `Tool ${toolId} transport failed calling ${url} after ${transportAttempt}/${TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS} attempts: ${diagnostic.message ?? 'unknown transport error'}`,
19911
+ input.connectionNeverOpened
19912
+ ? // Only these attempts are proven; a delivery before a resume
19913
+ // leaves no trace in this process, so never claim the call.
19914
+ `Tool ${toolId} could not open a connection for ${url} before the retry window closed. These connection attempts sent no request${earlierDeliveryMayHaveArrived ? ', but an earlier attempt of this call may have reached the provider' : ''}: ${diagnostic.message ?? 'unknown transport error'}`
19915
+ : `Tool ${toolId} transport failed calling ${url} after ${transportAttempt}/${TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS} attempts: ${diagnostic.message ?? 'unknown transport error'}`,
19322
19916
  null,
19323
19917
  0,
19324
19918
  'repairable',
@@ -19775,6 +20369,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19775
20369
  : url,
19776
20370
  {
19777
20371
  method: 'POST',
20372
+ // A followed redirect could fail to connect after
20373
+ // this POST was delivered, which would look like
20374
+ // a request that was never sent.
20375
+ redirect: 'manual',
19778
20376
  signal: abortController?.signal,
19779
20377
  headers: {
19780
20378
  'Content-Type': 'application/json',
@@ -19939,6 +20537,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19939
20537
  // graph; the row-scoped body handle below then
19940
20538
  // owns the measured value after this turn ends.
19941
20539
  bodyTurn.settleBytes(responseBodyBytes);
20540
+ const bodyWork =
20541
+ bodyTurn.waitForCooperativeWork?.(
20542
+ abortController?.signal,
20543
+ );
20544
+ if (bodyWork) await bodyWork;
19942
20545
  const transportBytes =
19943
20546
  this.memoryLedger.register({
19944
20547
  category: 'transportInFlight',
@@ -19964,6 +20567,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19964
20567
  nativeBatchBodyAttribution,
19965
20568
  );
19966
20569
  },
20570
+ abortController?.signal,
19967
20571
  );
19968
20572
  providerBodyAdmissionWaitMs += bodyRead.waitMs;
19969
20573
  } else {
@@ -19992,7 +20596,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19992
20596
  error,
19993
20597
  );
19994
20598
  }
20599
+ const bodyWork =
20600
+ bodyTurn.waitForCooperativeWork?.(
20601
+ abortController?.signal,
20602
+ );
20603
+ if (bodyWork) await bodyWork;
19995
20604
  },
20605
+ abortController?.signal,
19996
20606
  );
19997
20607
  providerBodyAdmissionWaitMs += bodyRead.waitMs;
19998
20608
  }
@@ -20342,8 +20952,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
20342
20952
  402,
20343
20953
  );
20344
20954
  }
20955
+ const connectionNeverOpened =
20956
+ response === null &&
20957
+ transportFailureProvesRequestNeverSent(transportError);
20345
20958
  const ambiguousDispatchedFailure =
20346
20959
  fetchDispatched &&
20960
+ !connectionNeverOpened &&
20347
20961
  (response === null ||
20348
20962
  error instanceof ToolExecuteResponseBodyTransportError ||
20349
20963
  error instanceof ToolExecuteInvalidJsonError);
@@ -20412,6 +21026,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
20412
21026
  : Date.now() - providerCallStartedAt,
20413
21027
  requestId: deeplineRequestId,
20414
21028
  aborted: abortController?.signal.aborted === true,
21029
+ connectionNeverOpened,
20415
21030
  });
20416
21031
  continue;
20417
21032
  } finally {
@@ -20426,6 +21041,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
20426
21041
  `Tool ${toolId} transport completed without an HTTP response.`,
20427
21042
  );
20428
21043
  }
21044
+ connectFailures = 0;
21045
+ firstConnectFailureAtMs = null;
21046
+ earlierDeliveryMayHaveArrived = true;
20429
21047
  span.setAttribute('plays.http_status_code', response.status);
20430
21048
 
20431
21049
  if (providerPermitReceiverRejected) {
@@ -20451,6 +21069,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
20451
21069
  );
20452
21070
  }
20453
21071
 
21072
+ if (response.status >= 300 && response.status < 400) {
21073
+ const location = response.headers.get('location');
21074
+ throw createToolHttpError(
21075
+ toolErrorSchemaVersion,
21076
+ `Tool ${toolId} was redirected to ${location ? transportGatewayOriginForDiagnostic(location) : '<none>'}; tool execution refuses redirects, so the outcome of this call is unknown.`,
21077
+ null,
21078
+ response.status,
21079
+ 'repairable',
21080
+ );
21081
+ }
21082
+
20454
21083
  if (!response.ok) {
20455
21084
  const text = responseErrorText ?? '';
20456
21085
  const recoveryKey = asyncRecoveryKey(invocationAttempt);
@@ -20683,10 +21312,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
20683
21312
  // not in question because there is nothing to replay. Every tool
20684
21313
  // may pace on it, including one that never declared transient
20685
21314
  // 5xx retries safe — otherwise a protective limit on the relay
20686
- // turns straight into customer row failures.
21315
+ // turns straight into customer row failures. A scheduler refusal
21316
+ // proves only that this delivery went nowhere: an earlier delivery
21317
+ // of the same attempt may have a row, so the attempt must not move.
20687
21318
  const shedKnownNotDispatched =
20688
21319
  failure.reason === 'concurrency_backpressure' ||
20689
- failure.reason === 'provider_pacing_wait';
21320
+ failure.reason === 'provider_pacing_wait' ||
21321
+ failure.reason === 'runtime_scheduler_capacity';
20690
21322
  if (
20691
21323
  failure.shouldRetry &&
20692
21324
  (failure.knownNotDispatched ||
@@ -20703,8 +21335,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
20703
21335
  failure.backpressureDelayMs !== null)
20704
21336
  ) {
20705
21337
  // A gateway follower is retrying its owner's durable
20706
- // invocation, while a concurrency or provider-pacing refusal
20707
- // wrote no invocation at all. Both reuse this logical attempt: advancing it
21338
+ // invocation, while a concurrency, provider-pacing or scheduler
21339
+ // refusal wrote no invocation itself. All reuse this logical attempt: advancing it
20708
21340
  // either bypasses the live-owner replay fence or makes the
20709
21341
  // prior-attempt sweep see an intentionally absent receipt.
20710
21342
  if (