deepline 0.3.461 → 0.3.463

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/tamradar.ts +17 -17
  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 +61 -0
  23. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-upstream-identity.ts +122 -2
  24. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-dead-letter.ts +11 -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 +38 -12
  111. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +33 -9
  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-6HJXNWUG.mjs → auth-LZLBR4C3.mjs} +5 -4
  130. package/dist/cli/auth-XPGKBJKB.js +41 -0
  131. package/dist/cli/{billing-6P5HO6QG.js → billing-6TJPNJQD.js} +152 -78
  132. package/dist/cli/{billing-MJ42N7YF.mjs → billing-OTOP5RE4.mjs} +84 -10
  133. package/dist/cli/{chunk-FCAZQ7WV.js → chunk-2NISBXMN.js} +52 -50
  134. package/dist/cli/{chunk-PNATXKLP.js → chunk-2ZIRCBYG.js} +6 -6
  135. package/dist/cli/{chunk-5FDKJQZE.js → chunk-3BOIOCNX.js} +1 -1
  136. package/dist/cli/chunk-3BVLLJ3H.mjs +1038 -0
  137. package/dist/cli/{chunk-FJZILBL7.mjs → chunk-3U5EAP6E.mjs} +5 -5
  138. package/dist/cli/{describe-FRAVCY5F.mjs → chunk-4YZKBBLZ.mjs} +19 -36
  139. package/dist/cli/{chunk-QWRHGGUH.js → chunk-5DRNNC72.js} +20 -2
  140. package/dist/cli/{chunk-Z2GUFYT6.js → chunk-5LEAXRHV.js} +11 -11
  141. package/dist/cli/{chunk-5MDIZX7M.js → chunk-5LHXOIPT.js} +8 -6
  142. package/dist/cli/{chunk-NMJ4D2WO.js → chunk-6PMCRYDA.js} +216 -91
  143. package/dist/cli/{chunk-EBU7LQ64.js → chunk-6V7KVRR5.js} +3 -3
  144. package/dist/cli/{chunk-MGXEUZHL.mjs → chunk-7D6X7IXW.mjs} +7 -5
  145. package/dist/cli/{chunk-SAZFZQRI.js → chunk-7FCYPAPP.js} +2 -2
  146. package/dist/cli/{chunk-ACCR7POB.js → chunk-7XYDDCQ2.js} +27 -27
  147. package/dist/cli/{chunk-5IYOUBFZ.js → chunk-ALZBANZI.js} +28 -54
  148. package/dist/cli/{chunk-3DNKFH2G.mjs → chunk-BAPTQDLF.mjs} +19 -1
  149. package/dist/cli/chunk-BGNWI3KH.js +827 -0
  150. package/dist/cli/{chunk-V7REJRDZ.mjs → chunk-C25QE4CH.mjs} +1 -1
  151. package/dist/cli/{chunk-FGMX4HUZ.js → chunk-CBZP6J3V.js} +29 -27
  152. package/dist/cli/{chunk-QEYHTIV4.mjs → chunk-CTQ56DXP.mjs} +10 -1
  153. package/dist/cli/{chunk-IHHLTHE7.mjs → chunk-CVQ7N5CV.mjs} +267 -38
  154. package/dist/cli/{chunk-7DH2FD7Y.mjs → chunk-D464JW3N.mjs} +5 -85
  155. package/dist/cli/{chunk-MEAIOXJL.mjs → chunk-DHXCBITQ.mjs} +7 -5
  156. package/dist/cli/chunk-F2RHUPVH.mjs +827 -0
  157. package/dist/cli/chunk-F6QV42MC.mjs +91 -0
  158. package/dist/cli/{chunk-OZBWRICM.js → chunk-FK2HNDCN.js} +2 -2
  159. package/dist/cli/{chunk-BGY2PLP7.mjs → chunk-GUT7CLOL.mjs} +23 -49
  160. package/dist/cli/{chunk-RY4IVOU3.mjs → chunk-I624J4H5.mjs} +16 -806
  161. package/dist/cli/{chunk-Z2XUZC34.js → chunk-IAYSWZDJ.js} +188 -76
  162. package/dist/cli/{chunk-FGLKOWSC.mjs → chunk-IKWRUNAP.mjs} +1 -1
  163. package/dist/cli/{chunk-KCCOQ5G5.js → chunk-JVDKZJUH.js} +511 -282
  164. package/dist/cli/{chunk-HDFRJ4IT.mjs → chunk-JVHOS3K3.mjs} +177 -65
  165. package/dist/cli/chunk-LYFKBLJI.mjs +1039 -0
  166. package/dist/cli/{chunk-SLEUWLJE.mjs → chunk-MSY22NQW.mjs} +5 -5
  167. package/dist/cli/{chunk-5FY72YOV.js → chunk-NAPTMYTY.js} +8 -8
  168. package/dist/cli/{chunk-O6QOMHCH.js → chunk-NDHJR2WZ.js} +37 -35
  169. package/dist/cli/{chunk-KXMNM4AQ.js → chunk-NMU3VR5U.js} +4 -5
  170. package/dist/cli/chunk-NTLI6P6Y.mjs +19 -0
  171. package/dist/cli/{chunk-RFONC4WJ.mjs → chunk-O3BJYD6D.mjs} +3 -3
  172. package/dist/cli/{describe-2WQY7ZCY.js → chunk-PVY4DPPF.js} +27 -44
  173. package/dist/cli/{chunk-WIYWSYDS.mjs → chunk-Q2EIZKVX.mjs} +3 -3
  174. package/dist/cli/{chunk-VZBCEA5S.mjs → chunk-RD6IQ2JY.mjs} +223 -92
  175. package/dist/cli/chunk-RGOUCIIZ.js +1039 -0
  176. package/dist/cli/{chunk-EIMG75BT.mjs → chunk-S3A4IXBQ.mjs} +4 -5
  177. package/dist/cli/{chunk-QIW7TVV6.mjs → chunk-S4XQCFES.mjs} +14 -12
  178. package/dist/cli/chunk-SKU6ECIY.js +19 -0
  179. package/dist/cli/{chunk-CRYOUPEB.js → chunk-TIRBDQ3W.js} +16 -16
  180. package/dist/cli/{chunk-G7L27KZB.mjs → chunk-TKAQR3IB.mjs} +31 -21
  181. package/dist/cli/{chunk-MDJPEGVN.js → chunk-UC4SFYNU.js} +392 -261
  182. package/dist/cli/{chunk-3BMLICF7.mjs → chunk-UIURR26V.mjs} +13 -8
  183. package/dist/cli/{chunk-TNMBPMMC.js → chunk-UROQDCVW.js} +13 -4
  184. package/dist/cli/{chunk-R64BGGRQ.mjs → chunk-VDGTTH7K.mjs} +2 -2
  185. package/dist/cli/chunk-VLEVVDWG.js +91 -0
  186. package/dist/cli/{chunk-R2GLGLDR.mjs → chunk-VPHN2HAL.mjs} +216 -91
  187. package/dist/cli/chunk-VY2GBNTX.js +1038 -0
  188. package/dist/cli/{chunk-CFUTOWE4.js → chunk-WG3KGAII.js} +8 -88
  189. package/dist/cli/{chunk-QAJAJPKK.mjs → chunk-WUMIYOXQ.mjs} +4 -2
  190. package/dist/cli/{chunk-SCIR5JXW.js → chunk-WUZS2HK6.js} +56 -51
  191. package/dist/cli/{chunk-T3MVIPHG.mjs → chunk-XDWS2JJT.mjs} +1 -1
  192. package/dist/cli/{chunk-KGQNIA37.js → chunk-XQCTG5OO.js} +44 -34
  193. package/dist/cli/{chunk-KVJ5VHW5.mjs → chunk-XW2JIYG7.mjs} +2 -2
  194. package/dist/cli/chunk-YGGIZ3VT.js +920 -0
  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-NX7NO3O6.js → csv-EH5QWR4O.js} +13 -12
  200. package/dist/cli/{csv-DQ6GW5B5.mjs → csv-OK45Z5XQ.mjs} +4 -3
  201. package/dist/cli/{db-R72DFMP6.js → db-3ESJKETS.js} +30 -27
  202. package/dist/cli/{db-2YP33M4K.mjs → db-6Z5E4HKN.mjs} +10 -7
  203. package/dist/cli/describe-PNDQB7YS.mjs +48 -0
  204. package/dist/cli/describe-QUF7VTTR.js +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-WFBU7L2Q.mjs → executions-2NTWQFEG.mjs} +7 -5
  210. package/dist/cli/{executions-ZLHD4QIT.js → executions-KUOBRX5V.js} +9 -7
  211. package/dist/cli/{feedback-CN3HJFVF.mjs → feedback-7RPRMSGY.mjs} +6 -5
  212. package/dist/cli/{feedback-NDV6TPHG.js → feedback-UPHAZJ4C.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-QAD6E3HW.mjs → git-SCYP6WSZ.mjs} +5 -4
  218. package/dist/cli/git-UH653WHV.js +25 -0
  219. package/dist/cli/index.js +152 -142
  220. package/dist/cli/index.mjs +56 -46
  221. package/dist/cli/{legacy-noops-KVC5QJ74.js → legacy-noops-7XMFCVRG.js} +8 -7
  222. package/dist/cli/{legacy-noops-TPTKNBI2.mjs → legacy-noops-A4KYAGI6.mjs} +5 -4
  223. package/dist/cli/{monitors-SV57JFLO.mjs → monitors-FLC5J7RI.mjs} +19 -6
  224. package/dist/cli/{monitors-KCROV6PJ.js → monitors-NZV6IX5H.js} +103 -90
  225. package/dist/cli/{org-NJYX5743.js → org-IZ7UP3XF.js} +70 -67
  226. package/dist/cli/{org-KX6QIR7A.mjs → org-LUETJ7Z5.mjs} +9 -6
  227. package/dist/cli/pages-22ML3O7O.js +252 -0
  228. package/dist/cli/pages-EUX5EAGF.mjs +252 -0
  229. package/dist/cli/{play-IQQ57L7Z.mjs → play-CDHPAKKM.mjs} +512 -1508
  230. package/dist/cli/{play-32L7EQN4.js → play-USJBQADG.js} +1090 -2086
  231. package/dist/cli/{providers-4WEC2EQN.mjs → providers-CCXZ3SQF.mjs} +7 -5
  232. package/dist/cli/{providers-ZQV6QLVQ.js → providers-WFEUNPGC.js} +10 -8
  233. package/dist/cli/{quickstart-HKUSFADF.js → quickstart-O4JSUORZ.js} +13 -12
  234. package/dist/cli/{quickstart-RQ76RIUE.mjs → quickstart-ZUULI635.mjs} +5 -4
  235. package/dist/cli/run-ISDJYJMU.js +443 -0
  236. package/dist/cli/run-QJ4NX5F5.mjs +443 -0
  237. package/dist/cli/{search-ZRAVFBZC.mjs → search-IX3XKZJ3.mjs} +8 -6
  238. package/dist/cli/search-TIFNLRPM.js +32 -0
  239. package/dist/cli/{secrets-CMJOVNVH.mjs → secrets-7VUDXTUZ.mjs} +7 -5
  240. package/dist/cli/{secrets-J3G5Y43U.js → secrets-ELQSNCTR.js} +16 -14
  241. package/dist/cli/{sessions-WY7VN4AE.js → sessions-A3FG3AZX.js} +6 -5
  242. package/dist/cli/{sessions-MDQYY5CB.mjs → sessions-VOIQ62GB.mjs} +5 -4
  243. package/dist/cli/{settings-4RWZPCT5.mjs → settings-S6N6EFBG.mjs} +14 -11
  244. package/dist/cli/{settings-HTNTFKCE.js → settings-ZTXQEMZI.js} +80 -77
  245. package/dist/cli/{setup-P3GYYZWG.mjs → setup-AYPIK4JE.mjs} +12 -10
  246. package/dist/cli/{setup-QGCAP62X.js → setup-Y3WOIFLS.js} +53 -51
  247. package/dist/cli/{skills-7NAE4ICL.mjs → skills-5SF5QWNI.mjs} +7 -6
  248. package/dist/cli/skills-MWFRMG3H.js +31 -0
  249. package/dist/cli/tools-3XNZ4J64.mjs +55 -0
  250. package/dist/cli/tools-XEC4OD3R.js +55 -0
  251. package/dist/cli/{update-GODLRKNB.mjs → update-3U4OVX5R.mjs} +9 -8
  252. package/dist/cli/update-DWYEYTZE.js +33 -0
  253. package/dist/cli/{workflow-4BXOUVEO.mjs → workflow-2M5JP7SA.mjs} +10 -8
  254. package/dist/cli/{workflow-AO3A6LXN.js → workflow-RM7H7555.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.ts → compiler-manifest-Dcyd3-Zd.d.mts} +43 -1
  260. package/dist/{compiler-manifest-CWz_sNOQ.d.mts → 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 +16 -12
  268. package/dist/index.mjs +15 -11
  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-IACERYQH.js +0 -40
  289. package/dist/cli/chunk-PQUUWFXY.js +0 -95
  290. package/dist/cli/chunk-YBWAG72P.mjs +0 -95
  291. package/dist/cli/chunk-YQDUODZW.js +0 -1710
  292. package/dist/cli/git-JVI36ZFV.js +0 -24
  293. package/dist/cli/search-75X5LTDE.js +0 -30
  294. package/dist/cli/skills-BKJ53JOQ.js +0 -30
  295. package/dist/cli/tools-COWMIEJ6.mjs +0 -45
  296. package/dist/cli/tools-TASOCE7I.js +0 -45
  297. package/dist/cli/update-SJIKVME6.js +0 -32
@@ -53,6 +53,7 @@ import { RUNTIME_CLAIM_HEARTBEAT_DIVISOR } from '../runtime-constants';
53
53
  import { runtimeLimitExceededCause } from '../run-failure';
54
54
  import {
55
55
  playRunRecoveryLogDetail,
56
+ playRunRecoveryProviderText,
56
57
  recoveryForDaytonaSandboxAcquisitionUnavailable,
57
58
  recoveryForRuntimeInitializationTransportFailure,
58
59
  recoveryForRuntimeSchedulerAdmissionFailure,
@@ -66,10 +67,14 @@ import type {
66
67
  PlayRunnerExecutionConfig,
67
68
  PlayRunnerResult,
68
69
  } from '../protocol';
70
+ import { resolveRunnerHeartbeatSilenceMs } from '../test-runtime-seams';
69
71
  import {
70
72
  RuntimeResourceFenceLostError,
71
73
  type PlayRunnerRuntimeResource,
72
74
  } from '../runner-backends/types';
75
+ import {
76
+ daytonaSandboxMatchesOperationIdentity,
77
+ } from '../runner-backends/backends/daytona-labels';
73
78
  import type { PlayBundleArtifact } from '../../plays/artifact-types';
74
79
  import {
75
80
  readRuntimeSheetRowOutcomeCounts,
@@ -161,8 +166,13 @@ import {
161
166
  markPostgresSchedulerAttemptWaiting,
162
167
  materializePostgresSchedulerFiles,
163
168
  listPostgresSchedulerCompletedRunnerTerminals,
169
+ listPostgresSchedulerDetachedRunnerLivenessWork,
164
170
  type PostgresSchedulerCompletedRunnerTerminal,
171
+ type PostgresSchedulerStaleDetachedRunner,
172
+ type PostgresSchedulerUnsettledRunnerTerminal,
173
+ type RunLedgerOutboxWithClient,
165
174
  listPostgresSchedulerAmbiguousModalCreateIntents,
175
+ recordPostgresSchedulerDetachedRunnerLivenessVerdict,
166
176
  recordPostgresSchedulerAttemptRuntimeResource,
167
177
  recordPostgresSchedulerAmbiguousModalCreateIntent,
168
178
  recordPostgresSchedulerLateManagedSandboxOperationForCleanup,
@@ -173,6 +183,7 @@ import {
173
183
  recordPostgresSchedulerRuntimeLifecycleEvent,
174
184
  recordPostgresSchedulerSandboxCrashDiagnostic,
175
185
  recordPostgresSchedulerSandboxCleanupEvent,
186
+ readPostgresSchedulerSandboxDeleteAcceptance,
176
187
  recordPostgresSchedulerRunnerTerminal,
177
188
  resumePostgresSchedulerDetachedRunnerAttempt,
178
189
  retryPostgresSchedulerComputeBillingJob,
@@ -204,8 +215,10 @@ import {
204
215
  type PostgresSchedulerOptions,
205
216
  type PostgresSchedulerQueryClient,
206
217
  type PostgresSchedulerSandboxCleanupJob,
218
+ type PostgresSchedulerSandboxDeleteAcceptance,
207
219
  type PostgresSchedulerSandboxCrashDiagnosticRequest,
208
220
  type PostgresSchedulerSignalRecord,
221
+ cancelPostgresSchedulerRun,
209
222
  } from './postgres';
210
223
  import {
211
224
  createPlayRunAttemptSuspensionJob,
@@ -224,15 +237,17 @@ import {
224
237
  } from './postgres-rate-state';
225
238
  import { verifyRuntimeReceiptGatewayHealth } from './runtime-receipt-gateway-health';
226
239
  import {
227
- runtimeSandboxPlacementPoliciesForWorkerEnv,
240
+ runtimeSandboxPlacementPoliciesRunnableByWorker,
228
241
  runtimeSandboxProviderCapabilities,
229
242
  } from './absurd-worker-required-env';
230
243
  import {
231
244
  acquirePgNotifyHub,
232
245
  isConnectionCategoryError,
233
246
  isRuntimeSchedulerCapacityError,
247
+ isRuntimeSchedulerCircuitOpenError,
234
248
  isRuntimeSchedulerQueryCancellationError,
235
249
  isRuntimeSchedulerRetryableTransactionError,
250
+ isRuntimeSchedulerServerUnavailableError,
236
251
  parseRuntimeSchedulerCapacityError,
237
252
  RuntimeSchedulerCapacityError,
238
253
  runtimeSchedulerCapacityRetryAfterMs,
@@ -296,7 +311,7 @@ import {
296
311
  bindRuntimeSandboxCapacityLease,
297
312
  createRuntimeSandboxOperationReservation,
298
313
  readReservedDaytonaCancellationOperation,
299
- recordRecoveredDaytonaCancellationSandbox,
314
+ recordDaytonaSandboxCleanupIntent,
300
315
  reserveRuntimeSandboxOperation,
301
316
  runtimeSandboxCapacityAdmissionMode,
302
317
  RuntimeSandboxCapacityLimitError,
@@ -828,6 +843,7 @@ export type AbsurdRuntimeWorkerDeps = {
828
843
  deleteRuntimeSandboxWithOutcome?: (
829
844
  sandboxId: string,
830
845
  expectedOrganizationId: string | null,
846
+ deleteAcceptance?: PostgresSchedulerSandboxDeleteAcceptance | null,
831
847
  ) => Promise<DaytonaSandboxDeleteOutcome>;
832
848
  /** Maximum time a best-effort sandbox deletion may occupy a worker slot. */
833
849
  sandboxGcTimeoutMs?: number;
@@ -859,6 +875,36 @@ function defaultProgressReporter(
859
875
  });
860
876
  }
861
877
 
878
+ /** What projecting a committed terminal needs: the progress fold and the
879
+ * run-ledger outbox delivery a lane worker runs after its own terminal. */
880
+ export type AbsurdRuntimeTerminalProjectionDeps = Pick<
881
+ AbsurdRuntimeWorkerDeps,
882
+ 'runWithClient' | 'createProgressReporter' | 'projectOutbox'
883
+ >;
884
+
885
+ export function createAbsurdRuntimeTerminalProjectionDeps(input: {
886
+ runWithClient: AbsurdRuntimeWorkerDeps['runWithClient'];
887
+ outboxWithClient: RunLedgerOutboxWithClient;
888
+ }): AbsurdRuntimeTerminalProjectionDeps {
889
+ return {
890
+ runWithClient: input.runWithClient,
891
+ createProgressReporter: defaultProgressReporter,
892
+ projectOutbox: async (launch, runId, options, targetEventId, signal) =>
893
+ await createRunLedgerProjectionJob({
894
+ outbox: createGatewayRunLedgerProjectionAdapter({
895
+ runId,
896
+ launch,
897
+ runtimeApi: {
898
+ ...absurdRuntimeApiFromLaunch(launch),
899
+ ...(signal ? { signal } : {}),
900
+ },
901
+ withClient: input.outboxWithClient,
902
+ schedulerOptions: options,
903
+ }),
904
+ }).deliver(targetEventId ? { targetEventId } : {}),
905
+ };
906
+ }
907
+
862
908
  function buildDefaultDeps(absurd: Absurd): AbsurdRuntimeWorkerDeps {
863
909
  // Resolve the pool lazily: when a caller (tests) overrides `runWithClient`,
864
910
  // the runtime scheduler URL is never required.
@@ -868,29 +914,18 @@ function buildDefaultDeps(absurd: Absurd): AbsurdRuntimeWorkerDeps {
868
914
  process.env.DEEPLINE_PLAY_RUNNER_BACKEND || 'daytona',
869
915
  );
870
916
  return {
871
- runWithClient: (fn, trace) =>
872
- withRuntimeSchedulerClient(getPool(), fn, trace),
917
+ ...createAbsurdRuntimeTerminalProjectionDeps({
918
+ runWithClient: (fn, trace) =>
919
+ withRuntimeSchedulerClient(getPool(), fn, trace),
920
+ outboxWithClient: (operation, trace) =>
921
+ withRuntimeSchedulerClient(getPool(), operation, trace),
922
+ }),
873
923
  absurd,
874
924
  configuredRuntimeBackend,
875
925
  resolveBackend: (backendId, runtimeSandboxPlacementPolicyId) =>
876
926
  runtimeSandboxPlacementPolicyId
877
927
  ? resolveRuntimeSandboxPlacementBackend(runtimeSandboxPlacementPolicyId)
878
928
  : resolvePlayRunnerBackend(backendId),
879
- createProgressReporter: defaultProgressReporter,
880
- projectOutbox: async (launch, runId, options, targetEventId, signal) =>
881
- await createRunLedgerProjectionJob({
882
- outbox: createGatewayRunLedgerProjectionAdapter({
883
- runId,
884
- launch,
885
- runtimeApi: {
886
- ...absurdRuntimeApiFromLaunch(launch),
887
- ...(signal ? { signal } : {}),
888
- },
889
- withClient: (operation, trace) =>
890
- withRuntimeSchedulerClient(getPool(), operation, trace),
891
- schedulerOptions: options,
892
- }),
893
- }).deliver(targetEventId ? { targetEventId } : {}),
894
929
  runtimeApiFromLaunch: absurdRuntimeApiFromLaunch,
895
930
  mintAttemptExecutorToken: requestAttemptExecutorToken,
896
931
  deleteRuntimeSandbox: (sandboxId) =>
@@ -900,10 +935,15 @@ function buildDefaultDeps(absurd: Absurd): AbsurdRuntimeWorkerDeps {
900
935
  }),
901
936
  deleteRuntimeSandboxRef: (ref) =>
902
937
  defaultRuntimeSandboxReconciliation.delete(ref),
903
- deleteRuntimeSandboxWithOutcome: (sandboxId, expectedOrganizationId) =>
938
+ deleteRuntimeSandboxWithOutcome: (
939
+ sandboxId,
940
+ expectedOrganizationId,
941
+ deleteAcceptance,
942
+ ) =>
904
943
  deleteDaytonaSandboxByIdWithOutcome({
905
944
  sandboxId,
906
945
  expectedOrganizationId,
946
+ deleteAcceptance,
907
947
  timeoutSeconds: ABSURD_SANDBOX_GC_TIMEOUT_MS / 1_000,
908
948
  }),
909
949
  sandboxGcTimeoutMs: ABSURD_SANDBOX_GC_TIMEOUT_MS,
@@ -1511,6 +1551,7 @@ export async function parkAndResolveSuspension(input: {
1511
1551
  * durable park can wake at the runner's heartbeat deadline. */
1512
1552
  readRunnerLiveness?: (runnerAttempt: number) => Promise<{
1513
1553
  heartbeatAt: string | null;
1554
+ heartbeatIdentity?: string | null;
1514
1555
  updatedAt: string | null;
1515
1556
  status: string;
1516
1557
  } | null>;
@@ -1542,15 +1583,11 @@ export async function parkAndResolveSuspension(input: {
1542
1583
  if (existing) {
1543
1584
  return absurdCheckpointBase(checkpoint);
1544
1585
  }
1545
- // Legacy parked rows predate heartbeatTimeoutMs. Never let malformed
1546
- // durable JSON turn the deadline into NaN, which Absurd treats as no timer.
1547
- const configuredHeartbeatTimeoutMs = Number(suspension.heartbeatTimeoutMs);
1548
- const heartbeatTimeoutMs =
1549
- Number.isFinite(configuredHeartbeatTimeoutMs) &&
1550
- configuredHeartbeatTimeoutMs >= 0
1551
- ? configuredHeartbeatTimeoutMs
1552
- : RUNTIME_CLAIM_LEASE_SECONDS * 1000;
1586
+ // Never let malformed durable JSON turn the deadline into NaN, which Absurd
1587
+ // treats as no timer.
1588
+ const heartbeatTimeoutMs = detachedRunnerHeartbeatTimeoutMs(suspension);
1553
1589
  const ceilingDeadlineMs = suspension.startedAtMs + suspension.ceilingMs;
1590
+ let silenceConfirmation: PlayCheckpoint['detachedRunnerSilenceConfirmation'];
1554
1591
  const readRunnerTerminal = input.readRunnerTerminal;
1555
1592
  const readRunnerLiveness = input.readRunnerLiveness;
1556
1593
  let rearms = 0;
@@ -1616,7 +1653,7 @@ export async function parkAndResolveSuspension(input: {
1616
1653
  let quietUntilMs = 0;
1617
1654
  const verdict = await confirmDetachedRunnerSilence({
1618
1655
  ctx,
1619
- stepPrefix: `${suspension.boundaryId}:liveness-confirm:${Number.isFinite(renewedAtMs) ? renewedAtMs : 'none'}`,
1656
+ stepPrefix: `${suspension.boundaryId}:liveness-confirm-heartbeat:${renewed?.heartbeatIdentity ?? renewed?.heartbeatAt ?? 'none'}`,
1620
1657
  cadenceMs: Math.floor(
1621
1658
  heartbeatTimeoutMs / RUNTIME_CLAIM_HEARTBEAT_DIVISOR,
1622
1659
  ),
@@ -1628,10 +1665,10 @@ export async function parkAndResolveSuspension(input: {
1628
1665
  if (Date.now() < quietUntilMs) return false;
1629
1666
  if (await readRunnerTerminal(suspension.runnerAttempt)) return true;
1630
1667
  const current = await readRunnerLiveness(suspension.runnerAttempt);
1631
- const currentAtMs = current?.heartbeatAt
1632
- ? Date.parse(current.heartbeatAt)
1633
- : Number.NaN;
1634
- if (Number.isFinite(currentAtMs) && !(currentAtMs <= renewedAtMs)) {
1668
+ if (
1669
+ (current?.heartbeatIdentity ?? current?.heartbeatAt ?? null) !==
1670
+ (renewed?.heartbeatIdentity ?? renewed?.heartbeatAt ?? null)
1671
+ ) {
1635
1672
  return true;
1636
1673
  }
1637
1674
  quietUntilMs = Date.now() + LIVENESS_REPLAY_READ_REUSE_MS;
@@ -1639,11 +1676,24 @@ export async function parkAndResolveSuspension(input: {
1639
1676
  },
1640
1677
  });
1641
1678
  if (verdict === 'responded') continue;
1679
+ if (verdict === 'gone') {
1680
+ silenceConfirmation = {
1681
+ boundaryId: suspension.boundaryId,
1682
+ runnerAttempt: suspension.runnerAttempt,
1683
+ heartbeatIdentity:
1684
+ renewed?.heartbeatIdentity ?? renewed?.heartbeatAt ?? null,
1685
+ };
1686
+ }
1642
1687
  break;
1643
1688
  }
1644
1689
  // Truth is the persisted terminal (read by the caller's wake path); the
1645
- // absurd event carries no payload we trust.
1646
- return absurdCheckpointBase(checkpoint);
1690
+ // absurd event carries no payload we trust. Discard any older proof when
1691
+ // this wake did not complete a confirmation of the same heartbeat.
1692
+ const resolved = absurdCheckpointBase(checkpoint);
1693
+ delete resolved.detachedRunnerSilenceConfirmation;
1694
+ if (silenceConfirmation)
1695
+ resolved.detachedRunnerSilenceConfirmation = silenceConfirmation;
1696
+ return resolved;
1647
1697
  }
1648
1698
 
1649
1699
  if (suspension.kind === 'sleep') {
@@ -1738,6 +1788,7 @@ export function createAbsurdEngineSuspensionAdapter(input: {
1738
1788
  readRunnerTerminal?: (runnerAttempt: number) => Promise<unknown | null>;
1739
1789
  readRunnerLiveness?: (runnerAttempt: number) => Promise<{
1740
1790
  heartbeatAt: string | null;
1791
+ heartbeatIdentity?: string | null;
1741
1792
  updatedAt: string | null;
1742
1793
  status: string;
1743
1794
  } | null>;
@@ -2334,8 +2385,8 @@ type ObservedTerminalSource =
2334
2385
  * outbox controller owns retry after this attempt, so a worker lease never
2335
2386
  * determines whether a completed Run becomes durable or eventually visible.
2336
2387
  */
2337
- function requestTerminalOutboxProjection(input: {
2338
- deps: AbsurdRuntimeWorkerDeps;
2388
+ type TerminalOutboxProjectionRequest = {
2389
+ deps: Pick<AbsurdRuntimeWorkerDeps, 'projectOutbox'>;
2339
2390
  launch: PlaySchedulerSubmitInput;
2340
2391
  runId: string;
2341
2392
  options: Pick<PostgresSchedulerOptions, 'schema'>;
@@ -2343,7 +2394,19 @@ function requestTerminalOutboxProjection(input: {
2343
2394
  trigger?: string;
2344
2395
  /** When this worker began post-commit projection for the terminal. */
2345
2396
  projectionStartedAtMs?: number;
2346
- }): void {
2397
+ signal?: AbortSignal;
2398
+ };
2399
+
2400
+ function requestTerminalOutboxProjection(
2401
+ input: TerminalOutboxProjectionRequest,
2402
+ ): void {
2403
+ void dispatchTerminalOutboxProjection(input);
2404
+ }
2405
+
2406
+ /** Never rejects: the terminal and its outbox fact are already committed. */
2407
+ function dispatchTerminalOutboxProjection(
2408
+ input: TerminalOutboxProjectionRequest,
2409
+ ): Promise<void> {
2347
2410
  const targetEventId = `run-terminal:${input.runId}`;
2348
2411
  const requestedAtMs = Date.now();
2349
2412
  // Terminal-visibility evidence: the request itself, and how long the
@@ -2357,8 +2420,14 @@ function requestTerminalOutboxProjection(input: {
2357
2420
  ? null
2358
2421
  : requestedAtMs - input.projectionStartedAtMs,
2359
2422
  });
2360
- void input.deps
2361
- .projectOutbox(input.launch, input.runId, input.options, targetEventId)
2423
+ return input.deps
2424
+ .projectOutbox(
2425
+ input.launch,
2426
+ input.runId,
2427
+ input.options,
2428
+ targetEventId,
2429
+ input.signal,
2430
+ )
2362
2431
  .then((projection) => {
2363
2432
  // Terminal-visibility latency evidence (ADR-0022 budget): when this
2364
2433
  // worker asked for delivery and how long the delivery took.
@@ -2635,21 +2704,21 @@ async function prepareAbsurdAttempt(input: {
2635
2704
  const launchBackend = normalizePlayRuntimeBackend(
2636
2705
  launch.runtimeBackend || configuredBackend,
2637
2706
  );
2638
- const configuredPlacementPolicies =
2639
- runtimeSandboxPlacementPoliciesForWorkerEnv(configuredBackend);
2707
+ const runnablePlacementPolicies =
2708
+ runtimeSandboxPlacementPoliciesRunnableByWorker(configuredBackend);
2640
2709
  const launchPlacementPolicy = resolveLaunchRuntimeSandboxPlacementPolicy({
2641
2710
  runtimeBackend: launchBackend,
2642
2711
  runtimeSandboxPlacementPolicyId: launch.runtimeSandboxPlacementPolicyId,
2643
2712
  });
2644
2713
  if (
2645
- launchBackend !== configuredBackend ||
2646
- (launchPlacementPolicy !== null &&
2647
- !configuredPlacementPolicies.some(
2648
- (policy) => policy.id === launchPlacementPolicy.id,
2649
- ))
2714
+ launchPlacementPolicy === null
2715
+ ? launchBackend !== configuredBackend
2716
+ : !runnablePlacementPolicies.some(
2717
+ (policy) => policy.id === launchPlacementPolicy.id,
2718
+ )
2650
2719
  ) {
2651
2720
  throw new DeferTask(
2652
- `Runtime lane placement mismatch: worker=${configuredPlacementPolicies.map((policy) => policy.id).join(',') || configuredBackend}, launch=${launchPlacementPolicy?.id ?? launchBackend}. Reconcile the namespace before retrying.`,
2721
+ `Runtime lane placement mismatch: worker=${runnablePlacementPolicies.map((policy) => policy.id).join(',') || configuredBackend}, launch=${launchPlacementPolicy?.id ?? launchBackend}. Reconcile the namespace before retrying.`,
2653
2722
  30,
2654
2723
  );
2655
2724
  }
@@ -2791,6 +2860,108 @@ async function prepareAbsurdAttempt(input: {
2791
2860
  );
2792
2861
  }
2793
2862
 
2863
+ function isRetryableAttemptPreparationError(error: unknown): boolean {
2864
+ return (
2865
+ isConnectionCategoryError(error) ||
2866
+ isRuntimeSchedulerServerUnavailableError(error) ||
2867
+ isRuntimeSchedulerCircuitOpenError(error) ||
2868
+ isRuntimeSchedulerRetryableTransactionError(error) ||
2869
+ isRuntimeSchedulerQueryCancellationError(error)
2870
+ );
2871
+ }
2872
+
2873
+ /**
2874
+ * Preparation runs before an attempt exists, so nothing downstream can settle
2875
+ * the run if it throws. A scheduler outage parks the task without spending an
2876
+ * engine attempt: a terminal write needs that same database, so a deadline here
2877
+ * could not settle anything sooner. Any other failure spends an attempt, and
2878
+ * the final one settles the run rather than leaving it queued with no task.
2879
+ */
2880
+ async function prepareAbsurdAttemptForTask(input: {
2881
+ ctx: TaskContext;
2882
+ deps: AbsurdRuntimeWorkerDeps;
2883
+ options: Pick<PostgresSchedulerOptions, 'schema'>;
2884
+ runId: string;
2885
+ }) {
2886
+ try {
2887
+ return await prepareAbsurdAttempt(input);
2888
+ } catch (error) {
2889
+ if (
2890
+ error instanceof DeferTask ||
2891
+ error instanceof SuspendTask ||
2892
+ isRuntimeSchedulerCapacityError(error)
2893
+ ) {
2894
+ throw error;
2895
+ }
2896
+ if (isRetryableAttemptPreparationError(error)) {
2897
+ const reason = error instanceof Error ? error.message : String(error);
2898
+ console.warn('[absurd-runtime.worker] attempt_preparation_deferred', {
2899
+ runId: input.runId,
2900
+ reason,
2901
+ });
2902
+ throw new DeferTask(`Attempt preparation deferred: ${reason}`, 30);
2903
+ }
2904
+ await settleUnstartedRunOnFinalPreparationFailure({ ...input, error });
2905
+ throw error;
2906
+ }
2907
+ }
2908
+
2909
+ async function settleUnstartedRunOnFinalPreparationFailure(input: {
2910
+ ctx: TaskContext;
2911
+ deps: AbsurdRuntimeWorkerDeps;
2912
+ options: Pick<PostgresSchedulerOptions, 'schema'>;
2913
+ runId: string;
2914
+ error: unknown;
2915
+ }): Promise<void> {
2916
+ const { ctx, deps, options, runId, error } = input;
2917
+ if (ctx.maxAttempts === null || ctx.attempt < ctx.maxAttempts) return;
2918
+ const reason = error instanceof Error ? error.message : String(error);
2919
+ try {
2920
+ // The fence skips a run that already has an attempt or changed state.
2921
+ const cancelled = await deps.runWithClient((client) =>
2922
+ cancelPostgresSchedulerRun(
2923
+ client,
2924
+ {
2925
+ runId,
2926
+ reason: `SCHEDULER_ATTEMPT_PREPARATION_FAILED: ${reason}`,
2927
+ onlyIfStrandedPreExecution: {
2928
+ releaseId: deps.currentReleaseId(),
2929
+ absurdTaskId: ctx.taskID,
2930
+ engineTaskTable: null,
2931
+ },
2932
+ },
2933
+ options,
2934
+ ),
2935
+ );
2936
+ console.error('[absurd-runtime.worker] attempt_preparation_exhausted', {
2937
+ runId,
2938
+ reason,
2939
+ cancelled,
2940
+ });
2941
+ } catch (settleError) {
2942
+ console.error(
2943
+ '[absurd-runtime.worker] attempt_preparation_settlement_failed',
2944
+ {
2945
+ runId,
2946
+ reason,
2947
+ error:
2948
+ settleError instanceof Error
2949
+ ? settleError.message
2950
+ : String(settleError),
2951
+ },
2952
+ );
2953
+ // The original error would fail the task and strand the run, so keep this
2954
+ // final attempt until the cancellation can commit.
2955
+ if (isRuntimeSchedulerCapacityError(settleError)) throw settleError;
2956
+ if (isRetryableAttemptPreparationError(settleError)) {
2957
+ throw new DeferTask(
2958
+ `Attempt preparation settlement deferred: ${reason}`,
2959
+ 30,
2960
+ );
2961
+ }
2962
+ }
2963
+ }
2964
+
2794
2965
  async function executeAbsurdAttemptAdapter(
2795
2966
  params: AbsurdPlayRunTaskParams,
2796
2967
  ctx: TaskContext,
@@ -2802,7 +2973,9 @@ async function executeAbsurdAttemptAdapter(
2802
2973
  schedulerOptions.schema ? { schema: schedulerOptions.schema } : {};
2803
2974
  const runId = params.runId;
2804
2975
 
2805
- let prepared = await prepareAbsurdAttempt({ deps, options, runId });
2976
+ const prepareAttempt = () =>
2977
+ prepareAbsurdAttemptForTask({ ctx, deps, options, runId });
2978
+ let prepared = await prepareAttempt();
2806
2979
  while (prepared.kind === 'created_projection_pending') {
2807
2980
  const delaySeconds = prepared.delaySeconds;
2808
2981
  // The preparation checkout is already released. Absurd retains a winning
@@ -2819,11 +2992,19 @@ async function executeAbsurdAttemptAdapter(
2819
2992
  } catch (error) {
2820
2993
  if (!(error instanceof TimeoutError)) throw error;
2821
2994
  }
2822
- prepared = await prepareAbsurdAttempt({ deps, options, runId });
2995
+ prepared = await prepareAttempt();
2823
2996
  if (signalled && prepared.kind === 'created_projection_pending') {
2824
- throw new Error(
2997
+ const error = new Error(
2825
2998
  `Run-created wake has no durable projection acknowledgement for ${runId}.`,
2826
2999
  );
3000
+ await settleUnstartedRunOnFinalPreparationFailure({
3001
+ ctx,
3002
+ deps,
3003
+ options,
3004
+ runId,
3005
+ error,
3006
+ });
3007
+ throw error;
2827
3008
  }
2828
3009
  }
2829
3010
  if (prepared.kind === 'missing') {
@@ -3297,11 +3478,15 @@ async function executeAssignedAbsurdAttempt(input: {
3297
3478
  options,
3298
3479
  ),
3299
3480
  );
3300
- const recordRunnerTerminal = (runnerAttempt: number, result: unknown) =>
3481
+ const recordRunnerTerminal = (
3482
+ runnerAttempt: number,
3483
+ result: unknown,
3484
+ expectedHeartbeatIdentity?: string | null,
3485
+ ) =>
3301
3486
  deps.runWithClient((client) =>
3302
3487
  recordPostgresSchedulerRunnerTerminal(
3303
3488
  client,
3304
- { runId, attempt: runnerAttempt, result },
3489
+ { runId, attempt: runnerAttempt, result, expectedHeartbeatIdentity },
3305
3490
  options,
3306
3491
  ),
3307
3492
  );
@@ -3328,46 +3513,19 @@ async function executeAssignedAbsurdAttempt(input: {
3328
3513
  fromDetachedRunner: input.fromDetachedRunner,
3329
3514
  terminalLogProducerAttempt: input.terminalLogProducerAttempt,
3330
3515
  });
3331
- const canarySelection = runtimeSandboxPlacementPolicy
3332
- ? selectRuntimeSandboxCanary({
3333
- policy: runtimeSandboxPlacementPolicy,
3334
- runId,
3335
- production: isRuntimeSandboxCanaryProduction({
3336
- nodeEnv: process.env.NODE_ENV,
3337
- runtimeSchedulerSchema: options.schema,
3338
- }),
3339
- })
3340
- : null;
3341
- if (
3342
- input.fromDetachedRunner &&
3343
- input.detachedSuspension &&
3344
- canarySelection?.canary === 'modal' &&
3345
- runtimeSandboxRefFromSuspension(input.detachedSuspension).provider ===
3346
- 'modal'
3347
- ) {
3348
- console.warn(
3349
- '[play-runner.sandbox_provider_canary]',
3350
- JSON.stringify({
3351
- runId,
3352
- workflowId: launch.runtimeAuthority?.workflowId ?? null,
3353
- runAttempt: generation,
3354
- policyId: runtimeSandboxPlacementPolicy?.id ?? null,
3355
- provider: 'modal',
3356
- bucket: canarySelection.bucket,
3357
- basisPoints: canarySelection.basisPoints,
3358
- runtimeEnvironment: 'production',
3359
- outcome: runnerResult.status,
3360
- phase: 'detached_terminal',
3361
- // A normal failed Play is customer/workflow truth, not evidence that
3362
- // Modal is unhealthy. Only a runner that disappeared after Modal
3363
- // accepted it is provider-infrastructure evidence at this boundary.
3364
- providerHealthFailure:
3365
- runnerResult.status === 'failed' &&
3366
- runnerResult.error.includes('RUNTIME_RUNNER_LOST:'),
3367
- errorName: null,
3368
- recoveryReason: null,
3516
+ if (input.fromDetachedRunner && input.detachedSuspension) {
3517
+ reportDetachedTerminalToModalCanary({
3518
+ launch,
3519
+ runId,
3520
+ attempt: generation,
3521
+ policy: runtimeSandboxPlacementPolicy,
3522
+ production: isRuntimeSandboxCanaryProduction({
3523
+ nodeEnv: process.env.NODE_ENV,
3524
+ runtimeSchedulerSchema: options.schema,
3369
3525
  }),
3370
- );
3526
+ suspension: input.detachedSuspension,
3527
+ runnerResult,
3528
+ });
3371
3529
  }
3372
3530
  const sandboxCrashDiagnostic =
3373
3531
  runnerResult.status === 'failed'
@@ -3813,6 +3971,9 @@ async function executeAssignedAbsurdAttempt(input: {
3813
3971
  // canonicalizes an omitted schema to the explicit production schema so
3814
3972
  // every runner-originated operation stays pinned to this physical lane.
3815
3973
  const absurdSchema = baseRunnerConfig.context.runtimeSchedulerSchema;
3974
+ const heartbeatSilenceMs = resolveRunnerHeartbeatSilenceMs(
3975
+ launch.runtimeTestFaultHeader,
3976
+ );
3816
3977
  return {
3817
3978
  runnerConfig: {
3818
3979
  ...baseRunnerConfig,
@@ -3830,6 +3991,7 @@ async function executeAssignedAbsurdAttempt(input: {
3830
3991
  runnerPushExecution: {
3831
3992
  leaseSeconds: RUNTIME_CLAIM_LEASE_SECONDS,
3832
3993
  runId,
3994
+ ...(heartbeatSilenceMs > 0 ? { heartbeatSilenceMs } : {}),
3833
3995
  },
3834
3996
  }
3835
3997
  : {}),
@@ -4493,10 +4655,33 @@ async function executeAssignedAbsurdAttempt(input: {
4493
4655
  providerOperationToken: reserved.providerOperationToken,
4494
4656
  resumeExistingProviderOperation:
4495
4657
  reserved.resumeExistingProviderOperation,
4658
+ resumePendingDelete: reserved.resumePendingDelete,
4496
4659
  existingProviderOperationAgeMs:
4497
4660
  reserved.existingProviderOperationAgeMs,
4498
4661
  };
4499
4662
  },
4663
+ async recordDaytonaCleanupIntent(intent) {
4664
+ const recorded = await deps.runWithClient((client) =>
4665
+ recordDaytonaSandboxCleanupIntent(
4666
+ client,
4667
+ {
4668
+ operationKey: intent.leaseId,
4669
+ providerOperationToken:
4670
+ intent.providerOperationToken,
4671
+ orgId: intent.orgId,
4672
+ runId: intent.runId,
4673
+ sandboxId: intent.sandboxId,
4674
+ routingDomain: intent.routingDomain,
4675
+ },
4676
+ options,
4677
+ ),
4678
+ );
4679
+ if (!recorded) {
4680
+ throw new Error(
4681
+ 'Daytona cleanup intent did not persist under its exact operation identity; cleanup and fallback suppressed.',
4682
+ );
4683
+ }
4684
+ },
4500
4685
  async releaseCapacity({ leaseId }) {
4501
4686
  await deps.runWithClient((client) =>
4502
4687
  abandonRuntimeSandboxOperationReservation(
@@ -4794,6 +4979,7 @@ async function executeAssignedAbsurdAttempt(input: {
4794
4979
  // Log-only. The persisted reason groups several provider conditions
4795
4980
  // into one decision; the detail names the one that was observed.
4796
4981
  const detail = playRunRecoveryLogDetail(recoveryError);
4982
+ const providerText = playRunRecoveryProviderText(recoveryError);
4797
4983
  console.warn('[absurd-runtime.worker] pre_code_recovery_deferred', {
4798
4984
  runId,
4799
4985
  generation,
@@ -4803,6 +4989,7 @@ async function executeAssignedAbsurdAttempt(input: {
4803
4989
  deadlineAtMs: recovery.deadlineAtMs,
4804
4990
  delaySeconds: recovery.delaySeconds,
4805
4991
  ...(detail ? { detail } : {}),
4992
+ ...(providerText ? { providerText } : {}),
4806
4993
  });
4807
4994
  throw new DeferTask(
4808
4995
  `Platform recovery deferred for ${recovery.dependency}: ${recovery.reason}`,
@@ -4878,48 +5065,22 @@ function createAbsurdWorkerTerminalProjectionAdapter(input: {
4878
5065
  fromDetachedRunner: boolean;
4879
5066
  terminalLogProducerAttempt?: number;
4880
5067
  }): PlayRunAttemptWorkerTerminalProjectionPort {
4881
- /**
4882
- * Every terminal releases what the attempt claimed. Best-effort by design:
4883
- * terminal truth is already durable, and a failed release must never turn a
4884
- * committed terminal into a worker error.
4885
- */
4886
- const releaseAttemptReceipts = async (reason: string): Promise<void> => {
4887
- await input.deps
4888
- .runWithClient((client) =>
4889
- releasePostgresSchedulerReceiptsForAttempt(
4890
- client,
4891
- { runId: input.runId, runAttempt: input.generation },
4892
- { ...input.options, orgId: input.launch.orgId },
4893
- ),
4894
- )
4895
- .then((released) => {
4896
- if (released.released > 0) {
4897
- console.info('[absurd-runtime.worker] released terminal receipts', {
4898
- runId: input.runId,
4899
- generation: input.generation,
4900
- reason,
4901
- releasedReceipts: released.released,
4902
- });
4903
- }
4904
- })
4905
- .catch((error) => {
4906
- console.warn(
4907
- '[absurd-runtime.worker] terminal receipt release failed',
4908
- {
4909
- runId: input.runId,
4910
- generation: input.generation,
4911
- reason,
4912
- error: error instanceof Error ? error.message : String(error),
4913
- },
4914
- );
4915
- });
5068
+ const attempt = {
5069
+ runWithClient: input.deps.runWithClient,
5070
+ launch: input.launch,
5071
+ runId: input.runId,
5072
+ generation: input.generation,
5073
+ options: input.options,
4916
5074
  };
4917
5075
  return createPlayRunAttemptWorkerTerminalProjection({
4918
5076
  async observeFenceLost() {
4919
5077
  // A lost fence means this attempt is dead, which a cancelled run is the
4920
5078
  // loudest case of: its receipts must not stay leased to a generation
4921
5079
  // that will never complete them.
4922
- await releaseAttemptReceipts('fence_lost');
5080
+ await releaseTerminalAttemptReceipts({
5081
+ ...attempt,
5082
+ reason: 'fence_lost',
5083
+ });
4923
5084
  await settleObservedTerminal({
4924
5085
  deps: input.deps,
4925
5086
  launch: input.launch,
@@ -4941,55 +5102,7 @@ function createAbsurdWorkerTerminalProjectionAdapter(input: {
4941
5102
  });
4942
5103
  },
4943
5104
  async releaseFailedAttempt() {
4944
- await releaseAttemptReceipts('failed_terminal');
4945
-
4946
- // A normal runner-reported failure does not enter the thrown-error
4947
- // cleanup path above. Its terminal is already durable, so leaving these
4948
- // sheet leases until their TTL would make a corrected forced rerun wait
4949
- // behind a run that can no longer write. Keep this best-effort exactly
4950
- // like receipt release: terminal truth never depends on cleanup.
4951
- const resources = await input.deps
4952
- .runWithClient((client) =>
4953
- readPostgresSchedulerRuntimeResourcesForRun(
4954
- client,
4955
- { runId: input.runId, attempt: input.generation },
4956
- input.options,
4957
- ),
4958
- )
4959
- .catch((error) => {
4960
- console.warn(
4961
- '[absurd-runtime.worker] failed terminal sheet-release resource read failed',
4962
- {
4963
- runId: input.runId,
4964
- generation: input.generation,
4965
- error: error instanceof Error ? error.message : String(error),
4966
- },
4967
- );
4968
- return [];
4969
- });
4970
- if (resources.length === 0) return;
4971
- try {
4972
- await releaseRuntimeSheetAttemptResources({
4973
- config: {
4974
- context: buildPostgresSchedulerRunnerContext(
4975
- input.launch,
4976
- input.generation,
4977
- ),
4978
- },
4979
- runId: input.runId,
4980
- runAttempt: input.generation,
4981
- resources,
4982
- });
4983
- } catch (error) {
4984
- console.warn(
4985
- '[absurd-runtime.worker] failed terminal sheet-attempt release failed',
4986
- {
4987
- runId: input.runId,
4988
- generation: input.generation,
4989
- error: error instanceof Error ? error.message : String(error),
4990
- },
4991
- );
4992
- }
5105
+ await releaseFailedTerminalAttempt(attempt);
4993
5106
  },
4994
5107
  queueProgressFinalization({ runnerResult }) {
4995
5108
  void input.reporter
@@ -5019,33 +5132,181 @@ function createAbsurdWorkerTerminalProjectionAdapter(input: {
5019
5132
  });
5020
5133
  }
5021
5134
 
5022
- function terminalEnvelopeFromRunnerResult(
5023
- result: PlayRunnerResult,
5024
- ): PlayRunAttemptTerminalEnvelope {
5025
- if (result.status === 'completed' || result.status === 'failed') {
5026
- return result;
5027
- }
5028
- throw new Error(
5029
- 'Attempt terminal projection received a suspended runner result before suspension resolution.',
5030
- );
5135
+ type TerminalAttemptRelease = {
5136
+ runWithClient: AbsurdRuntimeWorkerDeps['runWithClient'];
5137
+ launch: PlaySchedulerSubmitInput;
5138
+ runId: string;
5139
+ generation: number;
5140
+ options: Pick<PostgresSchedulerOptions, 'schema'>;
5141
+ };
5142
+
5143
+ /**
5144
+ * Every terminal releases what the attempt claimed. Best-effort by design:
5145
+ * terminal truth is already durable, and a failed release must never turn a
5146
+ * committed terminal into a worker error.
5147
+ */
5148
+ async function releaseTerminalAttemptReceipts(
5149
+ input: TerminalAttemptRelease & { reason: string },
5150
+ ): Promise<void> {
5151
+ await input
5152
+ .runWithClient((client) =>
5153
+ releasePostgresSchedulerReceiptsForAttempt(
5154
+ client,
5155
+ { runId: input.runId, runAttempt: input.generation },
5156
+ { ...input.options, orgId: input.launch.orgId },
5157
+ ),
5158
+ )
5159
+ .then((released) => {
5160
+ if (released.released > 0) {
5161
+ console.info('[absurd-runtime.worker] released terminal receipts', {
5162
+ runId: input.runId,
5163
+ generation: input.generation,
5164
+ reason: input.reason,
5165
+ releasedReceipts: released.released,
5166
+ });
5167
+ }
5168
+ })
5169
+ .catch((error) => {
5170
+ console.warn('[absurd-runtime.worker] terminal receipt release failed', {
5171
+ runId: input.runId,
5172
+ generation: input.generation,
5173
+ reason: input.reason,
5174
+ error: error instanceof Error ? error.message : String(error),
5175
+ });
5176
+ });
5031
5177
  }
5032
5178
 
5033
- const DAYTONA_RUNNER_CRASH_WITHOUT_TERMINAL =
5034
- /Daytona play runner exited with code .*without pushing a terminal \(crash containment epilogue\)/i;
5035
- const DAYTONA_RUNNER_LOST_WITHOUT_TERMINAL = /RUNTIME_RUNNER_LOST:/i;
5179
+ async function releaseFailedTerminalAttempt(
5180
+ input: TerminalAttemptRelease,
5181
+ ): Promise<void> {
5182
+ await releaseTerminalAttemptReceipts({
5183
+ ...input,
5184
+ reason: 'failed_terminal',
5185
+ });
5036
5186
 
5037
- function sandboxCrashDiagnosticRequest(input: {
5038
- error: string;
5039
- suspension?: DetachedRunnerSuspension;
5040
- }): PostgresSchedulerSandboxCrashDiagnosticRequest | undefined {
5041
- const suspension = input.suspension;
5042
- if (
5043
- !suspension ||
5044
- (!DAYTONA_RUNNER_CRASH_WITHOUT_TERMINAL.test(input.error) &&
5045
- !DAYTONA_RUNNER_LOST_WITHOUT_TERMINAL.test(input.error))
5046
- ) {
5047
- return undefined;
5048
- }
5187
+ // A normal runner-reported failure does not enter the thrown-error cleanup
5188
+ // path. Its terminal is already durable, so leaving these sheet leases until
5189
+ // their TTL would make a corrected forced rerun wait behind a run that can no
5190
+ // longer write. Keep this best-effort exactly like receipt release: terminal
5191
+ // truth never depends on cleanup.
5192
+ const resources = await input
5193
+ .runWithClient((client) =>
5194
+ readPostgresSchedulerRuntimeResourcesForRun(
5195
+ client,
5196
+ { runId: input.runId, attempt: input.generation },
5197
+ input.options,
5198
+ ),
5199
+ )
5200
+ .catch((error) => {
5201
+ console.warn(
5202
+ '[absurd-runtime.worker] failed terminal sheet-release resource read failed',
5203
+ {
5204
+ runId: input.runId,
5205
+ generation: input.generation,
5206
+ error: error instanceof Error ? error.message : String(error),
5207
+ },
5208
+ );
5209
+ return [];
5210
+ });
5211
+ if (resources.length === 0) return;
5212
+ try {
5213
+ await releaseRuntimeSheetAttemptResources({
5214
+ config: {
5215
+ context: buildPostgresSchedulerRunnerContext(
5216
+ input.launch,
5217
+ input.generation,
5218
+ ),
5219
+ },
5220
+ runId: input.runId,
5221
+ runAttempt: input.generation,
5222
+ resources,
5223
+ });
5224
+ } catch (error) {
5225
+ console.warn(
5226
+ '[absurd-runtime.worker] failed terminal sheet-attempt release failed',
5227
+ {
5228
+ runId: input.runId,
5229
+ generation: input.generation,
5230
+ error: error instanceof Error ? error.message : String(error),
5231
+ },
5232
+ );
5233
+ }
5234
+ }
5235
+
5236
+ function reportDetachedTerminalToModalCanary(input: {
5237
+ launch: PlaySchedulerSubmitInput;
5238
+ runId: string;
5239
+ attempt: number;
5240
+ policy: ReturnType<typeof resolveLaunchRuntimeSandboxPlacementPolicy>;
5241
+ production: boolean;
5242
+ suspension: DetachedRunnerSuspension;
5243
+ runnerResult: PlayRunnerResult;
5244
+ }): void {
5245
+ const selection = input.policy
5246
+ ? selectRuntimeSandboxCanary({
5247
+ policy: input.policy,
5248
+ runId: input.runId,
5249
+ production: input.production,
5250
+ })
5251
+ : null;
5252
+ if (
5253
+ selection?.canary !== 'modal' ||
5254
+ runtimeSandboxRefFromSuspension(input.suspension).provider !== 'modal'
5255
+ ) {
5256
+ return;
5257
+ }
5258
+ console.warn(
5259
+ '[play-runner.sandbox_provider_canary]',
5260
+ JSON.stringify({
5261
+ runId: input.runId,
5262
+ workflowId: input.launch.runtimeAuthority?.workflowId ?? null,
5263
+ runAttempt: input.attempt,
5264
+ policyId: input.policy?.id ?? null,
5265
+ provider: 'modal',
5266
+ bucket: selection.bucket,
5267
+ basisPoints: selection.basisPoints,
5268
+ runtimeEnvironment: 'production',
5269
+ outcome: input.runnerResult.status,
5270
+ phase: 'detached_terminal',
5271
+ // A normal failed Play is customer/workflow truth, not evidence that
5272
+ // Modal is unhealthy. Only a runner that disappeared after Modal
5273
+ // accepted it is provider-infrastructure evidence at this boundary.
5274
+ providerHealthFailure:
5275
+ input.runnerResult.status === 'failed' &&
5276
+ input.runnerResult.error.includes('RUNTIME_RUNNER_LOST:'),
5277
+ errorName: null,
5278
+ recoveryReason: null,
5279
+ }),
5280
+ );
5281
+ }
5282
+
5283
+ function terminalEnvelopeFromRunnerResult(
5284
+ result: PlayRunnerResult,
5285
+ ): PlayRunAttemptTerminalEnvelope {
5286
+ if (result.status === 'completed' || result.status === 'failed') {
5287
+ return result;
5288
+ }
5289
+ throw new Error(
5290
+ 'Attempt terminal projection received a suspended runner result before suspension resolution.',
5291
+ );
5292
+ }
5293
+
5294
+ const DAYTONA_RUNNER_CRASH_WITHOUT_TERMINAL =
5295
+ /Daytona play runner exited with code .*without pushing a terminal \(crash containment epilogue\)/i;
5296
+ const DAYTONA_RUNNER_LOST_WITHOUT_TERMINAL = /RUNTIME_RUNNER_LOST:/i;
5297
+
5298
+ function sandboxCrashDiagnosticRequest(input: {
5299
+ error: string;
5300
+ suspension?: DetachedRunnerSuspension;
5301
+ }): PostgresSchedulerSandboxCrashDiagnosticRequest | undefined {
5302
+ const suspension = input.suspension;
5303
+ if (
5304
+ !suspension ||
5305
+ (!DAYTONA_RUNNER_CRASH_WITHOUT_TERMINAL.test(input.error) &&
5306
+ !DAYTONA_RUNNER_LOST_WITHOUT_TERMINAL.test(input.error))
5307
+ ) {
5308
+ return undefined;
5309
+ }
5049
5310
  try {
5050
5311
  if (runtimeSandboxRefFromSuspension(suspension).provider !== 'daytona') {
5051
5312
  return undefined;
@@ -5199,9 +5460,11 @@ export async function resolveDetachedRunnerOutcome(input: {
5199
5460
  recordRunnerTerminal: (
5200
5461
  runnerAttempt: number,
5201
5462
  result: PlayRunnerResult,
5463
+ expectedHeartbeatIdentity?: string | null,
5202
5464
  ) => Promise<boolean>;
5203
5465
  readRunnerLiveness: () => Promise<{
5204
5466
  heartbeatAt: string | null;
5467
+ heartbeatIdentity?: string | null;
5205
5468
  updatedAt: string | null;
5206
5469
  status: string;
5207
5470
  } | null>;
@@ -5247,13 +5510,34 @@ export async function resolveDetachedRunnerOutcome(input: {
5247
5510
  },
5248
5511
  };
5249
5512
  };
5513
+ const suspend = (): PlayRunnerResult => ({
5514
+ status: 'suspended',
5515
+ suspension,
5516
+ logs: [],
5517
+ stats: {},
5518
+ steps: [],
5519
+ checkpoint: input.checkpoint,
5520
+ tableNamespace: null,
5521
+ });
5250
5522
  const settleFirstWriteWinner = async (
5251
5523
  candidate: PlayRunnerResult,
5524
+ expectedHeartbeatIdentity?: string | null,
5252
5525
  ): Promise<PlayRunnerResult> => {
5253
5526
  const attemptExists = await input.recordRunnerTerminal(
5254
5527
  suspension.runnerAttempt,
5255
5528
  candidate,
5529
+ expectedHeartbeatIdentity,
5256
5530
  );
5531
+ if (!attemptExists && expectedHeartbeatIdentity !== undefined) {
5532
+ // A heartbeat can win while sandbox inspection or the terminal write is
5533
+ // in flight. The SQL fence refuses only this synthetic outcome; preserve
5534
+ // a concurrent pushed result and otherwise confirm the new heartbeat.
5535
+ const winner = parseDetachedRunnerResult(
5536
+ await input.readRunnerTerminal(suspension.runnerAttempt),
5537
+ );
5538
+ if (winner) return settle(winner);
5539
+ if (await input.readRunnerLiveness()) return suspend();
5540
+ }
5257
5541
  if (!attemptExists) {
5258
5542
  throw new Error(
5259
5543
  `Detached runner attempt ${suspension.runnerAttempt} for ${input.runId} disappeared before terminal arbitration.`,
@@ -5297,51 +5581,31 @@ export async function resolveDetachedRunnerOutcome(input: {
5297
5581
  ? Math.max(0, now - heartbeatAtMs)
5298
5582
  : null;
5299
5583
  const reachedCeiling = now >= suspension.startedAtMs + suspension.ceilingMs;
5300
- const configuredHeartbeatTimeoutMs = Number(suspension.heartbeatTimeoutMs);
5301
- const heartbeatTimeoutMs =
5302
- Number.isFinite(configuredHeartbeatTimeoutMs) &&
5303
- configuredHeartbeatTimeoutMs >= 0
5304
- ? configuredHeartbeatTimeoutMs
5305
- : RUNTIME_CLAIM_LEASE_SECONDS * 1000;
5306
- const reparkDetachedRunner = (): PlayRunnerResult => ({
5307
- status: 'suspended',
5308
- suspension,
5309
- logs: [],
5310
- stats: {},
5311
- steps: [],
5312
- checkpoint: input.checkpoint,
5313
- tableNamespace: null,
5314
- });
5584
+ const heartbeatTimeoutMs = detachedRunnerHeartbeatTimeoutMs(suspension);
5315
5585
  if (!reachedCeiling && heartbeatAgeMs !== null) {
5316
5586
  if (heartbeatAgeMs < heartbeatTimeoutMs) {
5317
5587
  // We woke on an older deadline while the runner renewed liveness. Park
5318
5588
  // again from the new durable timestamp; do not touch Daytona.
5319
- return reparkDetachedRunner();
5589
+ return suspend();
5320
5590
  }
5321
5591
  }
5322
- const reportLivenessExpired = () => {
5323
- // This event is consumed directly by the operational-health dashboard.
5324
- // Keep its second argument strict JSON so Fly's text log stream remains an
5325
- // executable producer contract, independent of Bun's object-inspect format.
5326
- console.error(
5327
- '[absurd-runtime.worker] detached_runner_liveness_expired',
5328
- JSON.stringify({
5329
- runId: input.runId,
5330
- runnerAttempt: suspension.runnerAttempt,
5331
- sandboxId: suspension.sandboxId,
5332
- sessionId: suspension.sessionId,
5333
- cmdId: suspension.cmdId,
5334
- startedAtMs: suspension.startedAtMs,
5335
- heartbeatTimeoutMs,
5336
- ceilingMs: suspension.ceilingMs,
5337
- observedAtMs: now,
5338
- reachedCeiling,
5339
- liveness,
5340
- heartbeatAgeMs,
5341
- pushedTerminalPresent: false,
5342
- }),
5343
- );
5344
- };
5592
+ const confirmation = input.checkpoint.detachedRunnerSilenceConfirmation;
5593
+ const heartbeatIdentity =
5594
+ liveness?.heartbeatIdentity ?? liveness?.heartbeatAt ?? null;
5595
+ const silenceConfirmed = Boolean(
5596
+ confirmation &&
5597
+ confirmation.boundaryId === suspension.boundaryId &&
5598
+ confirmation.runnerAttempt === suspension.runnerAttempt &&
5599
+ confirmation.heartbeatIdentity === heartbeatIdentity,
5600
+ );
5601
+ const reportLivenessExpired = () =>
5602
+ reportDetachedRunnerLivenessExpired({
5603
+ runId: input.runId,
5604
+ suspension,
5605
+ observedAtMs: now,
5606
+ liveness,
5607
+ heartbeatAgeMs,
5608
+ });
5345
5609
  if (reachedCeiling) {
5346
5610
  reportLivenessExpired();
5347
5611
  return settleFirstWriteWinner({
@@ -5407,7 +5671,7 @@ export async function resolveDetachedRunnerOutcome(input: {
5407
5671
  }
5408
5672
  }
5409
5673
  // Inspection takes seconds. A terminal or heartbeat that landed meanwhile
5410
- // outranks everything read before it.
5674
+ // outranks everything read before it, including a sub-millisecond renewal.
5411
5675
  const storedDuringInspection = parseDetachedRunnerResult(
5412
5676
  await input.readRunnerTerminal(suspension.runnerAttempt),
5413
5677
  );
@@ -5415,23 +5679,92 @@ export async function resolveDetachedRunnerOutcome(input: {
5415
5679
  return settle(storedDuringInspection);
5416
5680
  }
5417
5681
  const latestLiveness = await input.readRunnerLiveness();
5418
- const latestHeartbeatAtMs = latestLiveness?.heartbeatAt
5419
- ? Date.parse(latestLiveness.heartbeatAt)
5420
- : Number.NaN;
5421
5682
  if (
5422
- Number.isFinite(latestHeartbeatAtMs) &&
5423
- !(latestHeartbeatAtMs <= heartbeatAtMs)
5683
+ (latestLiveness?.heartbeatIdentity ??
5684
+ latestLiveness?.heartbeatAt ??
5685
+ null) !== heartbeatIdentity
5424
5686
  ) {
5425
- return reparkDetachedRunner();
5687
+ return suspend();
5426
5688
  }
5689
+ if (!silenceConfirmed) return suspend();
5427
5690
  if (
5428
5691
  input.inspectDetachedRunner &&
5429
5692
  !provenGone &&
5430
5693
  !detachedRunnerInspectionProvesGone(inspection)
5431
5694
  ) {
5432
- return reparkDetachedRunner();
5695
+ return suspend();
5433
5696
  }
5434
5697
  reportLivenessExpired();
5698
+ return settleFirstWriteWinner(
5699
+ detachedRunnerLostResult({
5700
+ suspension,
5701
+ observedAtMs: now,
5702
+ livenessStatus: liveness?.status,
5703
+ heartbeatAtMs,
5704
+ heartbeatAgeMs,
5705
+ inspectionAttempted: Boolean(input.inspectDetachedRunner),
5706
+ inspection,
5707
+ inspectionFailed,
5708
+ }),
5709
+ heartbeatIdentity,
5710
+ );
5711
+ }
5712
+
5713
+ /** Legacy parked rows predate heartbeatTimeoutMs, and the field is durable JSON. */
5714
+ function detachedRunnerHeartbeatTimeoutMs(
5715
+ suspension: DetachedRunnerSuspension,
5716
+ ): number {
5717
+ const configured = Number(suspension.heartbeatTimeoutMs);
5718
+ return Number.isFinite(configured) && configured >= 0
5719
+ ? configured
5720
+ : RUNTIME_CLAIM_LEASE_SECONDS * 1000;
5721
+ }
5722
+
5723
+ function reportDetachedRunnerLivenessExpired(input: {
5724
+ runId: string;
5725
+ suspension: DetachedRunnerSuspension;
5726
+ observedAtMs: number;
5727
+ liveness: unknown;
5728
+ heartbeatAgeMs: number | null;
5729
+ }): void {
5730
+ const { suspension } = input;
5731
+ // This event is consumed directly by the operational-health dashboard.
5732
+ // Keep its second argument strict JSON so Fly's text log stream remains an
5733
+ // executable producer contract, independent of Bun's object-inspect format.
5734
+ console.error(
5735
+ '[absurd-runtime.worker] detached_runner_liveness_expired',
5736
+ JSON.stringify({
5737
+ runId: input.runId,
5738
+ runnerAttempt: suspension.runnerAttempt,
5739
+ sandboxId: suspension.sandboxId,
5740
+ sandboxProvider: suspension.sandboxProvider ?? 'daytona',
5741
+ sessionId: suspension.sessionId,
5742
+ cmdId: suspension.cmdId,
5743
+ startedAtMs: suspension.startedAtMs,
5744
+ heartbeatTimeoutMs: detachedRunnerHeartbeatTimeoutMs(suspension),
5745
+ ceilingMs: suspension.ceilingMs,
5746
+ observedAtMs: input.observedAtMs,
5747
+ reachedCeiling:
5748
+ input.observedAtMs >= suspension.startedAtMs + suspension.ceilingMs,
5749
+ liveness: input.liveness,
5750
+ heartbeatAgeMs: input.heartbeatAgeMs,
5751
+ pushedTerminalPresent: false,
5752
+ }),
5753
+ );
5754
+ }
5755
+
5756
+ /** The one failed terminal for a runner the provider proved gone. */
5757
+ function detachedRunnerLostResult(input: {
5758
+ suspension: DetachedRunnerSuspension;
5759
+ observedAtMs: number;
5760
+ livenessStatus: unknown;
5761
+ heartbeatAtMs: number;
5762
+ heartbeatAgeMs: number | null;
5763
+ inspectionAttempted: boolean;
5764
+ inspection: DetachedRunnerInspection | null;
5765
+ inspectionFailed: boolean;
5766
+ }): PlayRunnerResult {
5767
+ const { suspension, inspection } = input;
5435
5768
  const safeDiagnosticLabel = (value: unknown): string =>
5436
5769
  typeof value === 'string' && /^[a-z0-9_]{1,80}$/i.test(value)
5437
5770
  ? value
@@ -5439,36 +5772,37 @@ export async function resolveDetachedRunnerOutcome(input: {
5439
5772
  const runnerLiveness = {
5440
5773
  event: 'detached_runner_liveness_expired',
5441
5774
  runnerAttempt: suspension.runnerAttempt,
5442
- livenessStatus: safeDiagnosticLabel(liveness?.status),
5443
- lastHeartbeatAt: Number.isFinite(heartbeatAtMs)
5444
- ? new Date(heartbeatAtMs).toISOString()
5775
+ livenessStatus: safeDiagnosticLabel(input.livenessStatus),
5776
+ lastHeartbeatAt: Number.isFinite(input.heartbeatAtMs)
5777
+ ? new Date(input.heartbeatAtMs).toISOString()
5445
5778
  : null,
5446
- heartbeatAgeMs,
5447
- heartbeatTimeoutMs,
5448
- runElapsedMs: Math.max(0, now - suspension.startedAtMs),
5779
+ heartbeatAgeMs: input.heartbeatAgeMs,
5780
+ heartbeatTimeoutMs: detachedRunnerHeartbeatTimeoutMs(suspension),
5781
+ runElapsedMs: Math.max(0, input.observedAtMs - suspension.startedAtMs),
5449
5782
  runtimeCeilingMs: suspension.ceilingMs,
5450
- reachedRuntimeCeiling: reachedCeiling,
5783
+ reachedRuntimeCeiling:
5784
+ input.observedAtMs >= suspension.startedAtMs + suspension.ceilingMs,
5451
5785
  terminalResultFound: false,
5452
- inspectionAttempted: Boolean(input.inspectDetachedRunner),
5786
+ inspectionAttempted: input.inspectionAttempted,
5453
5787
  inspectionStage: inspection
5454
5788
  ? safeDiagnosticLabel(inspection.diagnosis?.stage)
5455
- : inspectionFailed
5789
+ : input.inspectionFailed
5456
5790
  ? 'inspection_failed'
5457
- : input.inspectDetachedRunner
5791
+ : input.inspectionAttempted
5458
5792
  ? 'inspection_empty'
5459
5793
  : 'not_configured',
5460
5794
  inspectionExitCode: inspection?.exitCode ?? null,
5461
5795
  inspectedResultStatus: inspection?.result?.status ?? null,
5462
5796
  };
5463
- return settleFirstWriteWinner({
5797
+ return {
5464
5798
  status: 'failed',
5465
- error: `RUNTIME_RUNNER_LOST: Detached runner heartbeat expired after ${heartbeatAgeMs ?? 'unknown'}ms without a terminal result. Deepline did not automatically retry because external actions may already have completed.`,
5799
+ error: `RUNTIME_RUNNER_LOST: Detached runner heartbeat expired after ${input.heartbeatAgeMs ?? 'unknown'}ms without a terminal result. Deepline did not automatically retry because external actions may already have completed.`,
5466
5800
  logs: [`[runtime.failure_diagnostic] ${JSON.stringify(runnerLiveness)}`],
5467
5801
  stats: { runnerLiveness },
5468
5802
  steps: [],
5469
5803
  checkpoint: null,
5470
5804
  tableNamespace: null,
5471
- });
5805
+ };
5472
5806
  }
5473
5807
 
5474
5808
  type RuntimeCleanupApiContext = Parameters<
@@ -6283,6 +6617,71 @@ async function cancelAbsurdEngineTaskAfterSchedulerTerminal(input: {
6283
6617
  }
6284
6618
  }
6285
6619
 
6620
+ type SettledRunnerTerminal = {
6621
+ terminal: PostgresSchedulerCompletedRunnerTerminal;
6622
+ launch: PlaySchedulerSubmitInput;
6623
+ runnerResult: PlayRunnerResult;
6624
+ schedulerStatus: 'completed' | 'failed';
6625
+ suspension: DetachedRunnerSuspension | null;
6626
+ /** Another writer committed this terminal and owns what follows it. */
6627
+ terminalizedByAnotherWorker: boolean;
6628
+ };
6629
+
6630
+ /**
6631
+ * What a lane worker does after its own terminal commits: fold the run's
6632
+ * durable facts into progress, finalize it, and ask for the terminal outbox
6633
+ * row. Never rejects: terminal truth is already durable, and the run-ledger
6634
+ * projector delivers the outbox row without this.
6635
+ */
6636
+ async function projectRecoveredRunnerTerminal(input: {
6637
+ deps: AbsurdRuntimeTerminalProjectionDeps;
6638
+ launch: PlaySchedulerSubmitInput;
6639
+ runId: string;
6640
+ attempt: number;
6641
+ runnerResult: PlayRunnerResult;
6642
+ options: Pick<PostgresSchedulerOptions, 'schema'>;
6643
+ signal?: AbortSignal;
6644
+ }): Promise<void> {
6645
+ const progress = (async () => {
6646
+ const reporter = input.deps.createProgressReporter(
6647
+ input.launch,
6648
+ input.attempt,
6649
+ );
6650
+ await reconcileTerminalTruthIntoReporter({
6651
+ deps: input.deps,
6652
+ launch: input.launch,
6653
+ runId: input.runId,
6654
+ attempt: input.attempt,
6655
+ options: input.options,
6656
+ reporter,
6657
+ });
6658
+ await reporter.finalize(input.runnerResult, {
6659
+ status: 'running',
6660
+ skipTerminalLogReplay: true,
6661
+ terminalLogProducerAttempt: input.attempt,
6662
+ });
6663
+ })().catch((error) => {
6664
+ console.warn(
6665
+ '[absurd-runtime.worker] recovered_terminal_projection_failed',
6666
+ {
6667
+ runId: input.runId,
6668
+ attempt: input.attempt,
6669
+ error: error instanceof Error ? error.message : String(error),
6670
+ },
6671
+ );
6672
+ });
6673
+ await Promise.all([
6674
+ progress,
6675
+ dispatchTerminalOutboxProjection({
6676
+ deps: input.deps,
6677
+ launch: input.launch,
6678
+ runId: input.runId,
6679
+ options: input.options,
6680
+ ...(input.signal ? { signal: input.signal } : {}),
6681
+ }),
6682
+ ]);
6683
+ }
6684
+
6286
6685
  /**
6287
6686
  * Close completed runner handoffs before this lane's claim loop starts.
6288
6687
  *
@@ -6299,9 +6698,11 @@ async function cancelAbsurdEngineTaskAfterSchedulerTerminal(input: {
6299
6698
  export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
6300
6699
  client: PostgresSchedulerQueryClient;
6301
6700
  /** A lane worker supplies post-terminal projection and sandbox cleanup. The
6302
- * fixed controller intentionally omits these: the terminal transaction's
6303
- * durable outbox is delivered by its singleton outbox controller, avoiding a
6304
- * worker → gateway retry chain during global recovery. */
6701
+ * fixed controller's lane-wide recovery intentionally omits these: the
6702
+ * terminal transaction's durable outbox is delivered by its singleton outbox
6703
+ * controller, avoiding a worker → gateway retry chain during global
6704
+ * recovery. The liveness sweep projects what it settles itself, inside its
6705
+ * pass budget. */
6305
6706
  deps?: AbsurdRuntimeWorkerDeps;
6306
6707
  queue: string;
6307
6708
  schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
@@ -6309,6 +6710,8 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
6309
6710
  limit?: number;
6310
6711
  /** Preselected by the fixed controller's one-query cross-lane scan. */
6311
6712
  terminals?: readonly PostgresSchedulerCompletedRunnerTerminal[];
6713
+ /** Told of each terminal now durable, whichever writer won it. */
6714
+ onTerminalSettled?: (settled: SettledRunnerTerminal) => void;
6312
6715
  }): Promise<{ recovered: number; skipped: number }> {
6313
6716
  const options = input.schedulerOptions ?? {};
6314
6717
  const terminals =
@@ -6356,6 +6759,22 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
6356
6759
  // outbox semantics instead of inventing a recovery-only terminal write.
6357
6760
  let schedulerStatus: 'completed' | 'failed';
6358
6761
  let terminalizedByAnotherWorker = false;
6762
+ const parsedSuspension = parseOverdueDetachedRunnerSuspension(
6763
+ terminal.suspension,
6764
+ );
6765
+ // The run's suspension can name a newer attempt's sandbox. Only this
6766
+ // terminal's own sandbox may supply its crash diagnostic or canary fact.
6767
+ const suspension =
6768
+ parsedSuspension?.runnerAttempt === terminal.attempt
6769
+ ? parsedSuspension
6770
+ : null;
6771
+ const sandboxCrashDiagnostic =
6772
+ runnerResult.status === 'failed' && suspension
6773
+ ? sandboxCrashDiagnosticRequest({
6774
+ error: runnerResult.error,
6775
+ suspension,
6776
+ })
6777
+ : undefined;
6359
6778
  try {
6360
6779
  await markPostgresSchedulerAttemptRunning(
6361
6780
  input.client,
@@ -6379,6 +6798,7 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
6379
6798
  {
6380
6799
  ...options,
6381
6800
  terminalLogOutbox: 'ctx_log_tail',
6801
+ ...(sandboxCrashDiagnostic ? { sandboxCrashDiagnostic } : {}),
6382
6802
  },
6383
6803
  );
6384
6804
  if (settled.status !== 'completed' && settled.status !== 'failed') {
@@ -6422,44 +6842,25 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
6422
6842
  } else {
6423
6843
  recovered += 1;
6424
6844
  }
6845
+ input.onTerminalSettled?.({
6846
+ terminal,
6847
+ launch,
6848
+ runnerResult,
6849
+ schedulerStatus,
6850
+ suspension,
6851
+ terminalizedByAnotherWorker,
6852
+ });
6425
6853
 
6426
6854
  // The worker which won the scheduler terminal can still disappear after
6427
6855
  // its atomic write but before best-effort projection. Cleanup is already
6428
6856
  // a durable obligation owned by the independent cleanup controller.
6429
6857
  if (input.deps) {
6430
- const workerDeps = input.deps;
6431
- void (async () => {
6432
- const reporter = workerDeps.createProgressReporter(
6433
- launch,
6434
- terminal.attempt,
6435
- );
6436
- await reconcileTerminalTruthIntoReporter({
6437
- deps: workerDeps,
6438
- launch,
6439
- runId: terminal.runId,
6440
- attempt: terminal.attempt,
6441
- options,
6442
- reporter,
6443
- });
6444
- await reporter.finalize(runnerResult, {
6445
- status: 'running',
6446
- skipTerminalLogReplay: true,
6447
- terminalLogProducerAttempt: terminal.attempt,
6448
- });
6449
- })().catch((error) => {
6450
- console.warn(
6451
- '[absurd-runtime.worker] recovered_terminal_projection_failed',
6452
- {
6453
- runId: terminal.runId,
6454
- attempt: terminal.attempt,
6455
- error: error instanceof Error ? error.message : String(error),
6456
- },
6457
- );
6458
- });
6459
- requestTerminalOutboxProjection({
6858
+ void projectRecoveredRunnerTerminal({
6460
6859
  deps: input.deps,
6461
6860
  launch,
6462
6861
  runId: terminal.runId,
6862
+ attempt: terminal.attempt,
6863
+ runnerResult,
6463
6864
  options,
6464
6865
  });
6465
6866
  }
@@ -6735,6 +7136,661 @@ export async function recoverAbsurdRuntimeOverdueDetachedRunnerCeilingsAcrossAll
6735
7136
  return recoverAbsurdRuntimeOverdueDetachedRunnerCeilings(input);
6736
7137
  }
6737
7138
 
7139
+ /** Inside `ABSURD_OVERDUE_DETACHED_RUNNER_RECOVERY_INTERVAL_MS`. */
7140
+ export const ABSURD_DETACHED_RUNNER_LIVENESS_PASS_BUDGET_MS = 10_000;
7141
+ /** The fixed controller stops waiting for a pass here, even on a stuck store. */
7142
+ export const ABSURD_DETACHED_RUNNER_LIVENESS_HARD_STOP_MS = 12_000;
7143
+ /** Reads hold memory in a 256 MiB controller, so keep them few. Hung reads keep
7144
+ * their slots across passes, which stops a stuck provider piling them up. */
7145
+ export const ABSURD_DETACHED_RUNNER_LIVENESS_READS_PER_PROVIDER = 3;
7146
+ /** Each provider's read has a shorter deadline of its own. This backstop gives
7147
+ * back the slot of any read that outlives it instead of blinding the sweep
7148
+ * until the controller restarts. */
7149
+ export const ABSURD_DETACHED_RUNNER_LIVENESS_READ_DEADLINE_MS = 60_000;
7150
+ /** Past this many unsettled reads a provider gets no more, which bounds what
7151
+ * a provider that never answers can hold. */
7152
+ export const ABSURD_DETACHED_RUNNER_LIVENESS_MAX_ABANDONED_READS =
7153
+ 2 * ABSURD_DETACHED_RUNNER_LIVENESS_READS_PER_PROVIDER;
7154
+ export const ABSURD_DETACHED_RUNNER_LIVENESS_PAGE_LIMIT = 100;
7155
+ /** How soon an unproven sandbox may be read again. */
7156
+ export const ABSURD_DETACHED_RUNNER_LIVENESS_RECHECK_MS = 60_000;
7157
+
7158
+ const ABSURD_DETACHED_RUNNER_LIVENESS_PROVIDERS = ['daytona', 'modal'] as const;
7159
+ /** Inspection stages that carry no answer from the provider about the sandbox. */
7160
+ const DETACHED_RUNNER_UNANSWERED_STAGES: ReadonlySet<string> = new Set([
7161
+ 'sandbox_lookup_failed',
7162
+ 'missing_routing_domain',
7163
+ 'wrong_routing_domain',
7164
+ ]);
7165
+ type DetachedRunnerLivenessProvider =
7166
+ (typeof ABSURD_DETACHED_RUNNER_LIVENESS_PROVIDERS)[number];
7167
+
7168
+ /**
7169
+ * Process-local on purpose. Losing it costs each candidate one extra read, and
7170
+ * `nextCheckAtMs` is bounded by construction: a pass adds at most a page per
7171
+ * provider, and every entry is pruned once its recheck time passes.
7172
+ */
7173
+ export type AbsurdDetachedRunnerLivenessSweepState = {
7174
+ generation: number;
7175
+ nextCheckAtMs: Map<string, number>;
7176
+ afterRunIds: Record<DetachedRunnerLivenessProvider, string | null>;
7177
+ readsInFlight: Record<DetachedRunnerLivenessProvider, number>;
7178
+ /** Reads past their deadline that have still not settled. */
7179
+ abandonedReads: Record<DetachedRunnerLivenessProvider, number>;
7180
+ /** A settlement runs, perhaps for a pass that has returned. No other starts
7181
+ * until it ends, so a stalled store holds one settlement, not one per pass. */
7182
+ settling: boolean;
7183
+ /** Likewise for a settled terminal's progress and outbox projection. */
7184
+ projecting: boolean;
7185
+ };
7186
+
7187
+ export function createAbsurdDetachedRunnerLivenessSweepState(): AbsurdDetachedRunnerLivenessSweepState {
7188
+ return {
7189
+ generation: 0,
7190
+ nextCheckAtMs: new Map(),
7191
+ afterRunIds: { daytona: null, modal: null },
7192
+ readsInFlight: { daytona: 0, modal: 0 },
7193
+ abandonedReads: { daytona: 0, modal: 0 },
7194
+ settling: false,
7195
+ projecting: false,
7196
+ };
7197
+ }
7198
+
7199
+ export type AbsurdDetachedRunnerLivenessOutcome = {
7200
+ stale: number;
7201
+ notDue: number;
7202
+ inspected: number;
7203
+ /** Enforce mode: recorded terminals this pass tried to settle. */
7204
+ recorded: number;
7205
+ unproven: number;
7206
+ proven: number;
7207
+ /** Observe mode: proven gone, and nothing was written. */
7208
+ wouldSettle: number;
7209
+ /** A heartbeat, terminal or cancellation moved during the read and won. */
7210
+ fenced: number;
7211
+ settled: number;
7212
+ settlementFailed: number;
7213
+ invalidSuspensions: number;
7214
+ /** No answer: the read failed, hung, or was not started. Never proof. */
7215
+ unreadable: number;
7216
+ /** Providers with due sandboxes and no answered read this pass, so their
7217
+ * silence in `wouldSettle` says nothing about false positives. */
7218
+ blindProviders: DetachedRunnerLivenessProvider[];
7219
+ budgetExhausted: boolean;
7220
+ };
7221
+
7222
+ async function runDetachedRunnerLivenessLane<T>(input: {
7223
+ items: readonly T[];
7224
+ concurrency: number;
7225
+ active: () => boolean;
7226
+ visit: (item: T) => Promise<void>;
7227
+ }): Promise<number> {
7228
+ let visited = 0;
7229
+ await Promise.all(
7230
+ Array.from(
7231
+ { length: Math.min(input.concurrency, input.items.length) },
7232
+ async () => {
7233
+ while (visited < input.items.length && input.active()) {
7234
+ const item = input.items[visited]!;
7235
+ visited += 1;
7236
+ await input.visit(item);
7237
+ }
7238
+ },
7239
+ ),
7240
+ );
7241
+ return visited;
7242
+ }
7243
+
7244
+ /**
7245
+ * One pass of the fixed controller's liveness sweep. Parked confirmation slices
7246
+ * wait behind their own org's backlog in org-ranked claims, which delayed 298
7247
+ * lost verdicts about 27 minutes on Oct 29, so this reads each stale sandbox
7248
+ * outside those claims. An ended sandbox settles at once. A running or
7249
+ * unreadable one is left alone, however long it stays silent.
7250
+ *
7251
+ * Discovery reads, then no scheduler client is held across a provider read, and
7252
+ * the verdict is one conditional first write. That write is the same runner
7253
+ * terminal the parked attempt would record, so a racing wake commits the same
7254
+ * result, and the same settlement path leaves the same cleanup, billing and
7255
+ * outbox facts on any lane. No customer code runs and nothing is replayed.
7256
+ * A terminal this pass commits is then projected and released as its parked
7257
+ * worker would have; one another writer committed is left to that writer.
7258
+ *
7259
+ * The pass waits on verdict writes, settlement and projection only until its
7260
+ * budget. A settlement or projection that overruns keeps its slot, so no pass
7261
+ * starts another until it ends; a verdict that commits late settles next pass.
7262
+ *
7263
+ * In `observe` mode the pass reads providers and reports what it would settle,
7264
+ * and writes nothing.
7265
+ */
7266
+ export async function recoverAbsurdRuntimeStaleDetachedRunners(input: {
7267
+ withClient: <T>(
7268
+ fn: (client: PostgresSchedulerQueryClient) => Promise<T>,
7269
+ ) => Promise<T>;
7270
+ state: AbsurdDetachedRunnerLivenessSweepState;
7271
+ mode: 'observe' | 'enforce';
7272
+ inspect?: (
7273
+ suspension: DetachedRunnerSuspension,
7274
+ ) => Promise<DetachedRunnerInspection | null>;
7275
+ schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
7276
+ /** Whether a Modal canary run's terminal counts as production evidence. */
7277
+ canaryProduction: boolean;
7278
+ /** Projects each terminal this sweep settles, as its parked worker would
7279
+ * have. Without it, only the run-ledger projector delivers the terminal. */
7280
+ projection?: AbsurdRuntimeTerminalProjectionDeps;
7281
+ pageLimit?: number;
7282
+ concurrency?: number;
7283
+ clock?: {
7284
+ now: () => number;
7285
+ budgetElapsed?: Promise<void>;
7286
+ readDeadline?: () => Promise<void>;
7287
+ };
7288
+ }): Promise<AbsurdDetachedRunnerLivenessOutcome> {
7289
+ const { state } = input;
7290
+ state.abandonedReads ??= { daytona: 0, modal: 0 };
7291
+ state.settling ??= false;
7292
+ state.projecting ??= false;
7293
+ const readDeadline =
7294
+ input.clock?.readDeadline ??
7295
+ (() =>
7296
+ new Promise<void>((resolve) => {
7297
+ setTimeout(
7298
+ resolve,
7299
+ ABSURD_DETACHED_RUNNER_LIVENESS_READ_DEADLINE_MS,
7300
+ ).unref?.();
7301
+ }));
7302
+ const answeredReads: Record<DetachedRunnerLivenessProvider, number> = {
7303
+ daytona: 0,
7304
+ modal: 0,
7305
+ };
7306
+ const options = input.schedulerOptions ?? {};
7307
+ const now = input.clock?.now ?? Date.now;
7308
+ const limit = input.pageLimit ?? ABSURD_DETACHED_RUNNER_LIVENESS_PAGE_LIMIT;
7309
+ const concurrency =
7310
+ input.concurrency ?? ABSURD_DETACHED_RUNNER_LIVENESS_READS_PER_PROVIDER;
7311
+ // A provider-state read only: see `skipCapturedOutput`.
7312
+ const inspect =
7313
+ input.inspect ??
7314
+ ((suspension: DetachedRunnerSuspension) =>
7315
+ defaultRuntimeSandboxReconciliation.inspectDetachedRunner(
7316
+ runtimeSandboxRefFromSuspension(suspension),
7317
+ suspension,
7318
+ { skipCapturedOutput: true },
7319
+ ));
7320
+ state.generation += 1;
7321
+ const generation = state.generation;
7322
+ const deadlineAtMs = now() + ABSURD_DETACHED_RUNNER_LIVENESS_PASS_BUDGET_MS;
7323
+ const active = () => state.generation === generation && now() < deadlineAtMs;
7324
+ for (const [key, dueAtMs] of state.nextCheckAtMs) {
7325
+ if (dueAtMs <= now()) state.nextCheckAtMs.delete(key);
7326
+ }
7327
+ const outcome: AbsurdDetachedRunnerLivenessOutcome = {
7328
+ stale: 0,
7329
+ notDue: 0,
7330
+ inspected: 0,
7331
+ recorded: 0,
7332
+ unproven: 0,
7333
+ proven: 0,
7334
+ wouldSettle: 0,
7335
+ fenced: 0,
7336
+ settled: 0,
7337
+ settlementFailed: 0,
7338
+ invalidSuspensions: 0,
7339
+ unreadable: 0,
7340
+ blindProviders: [],
7341
+ budgetExhausted: false,
7342
+ };
7343
+ const work = await input.withClient((client) =>
7344
+ listPostgresSchedulerDetachedRunnerLivenessWork(
7345
+ client,
7346
+ {
7347
+ limit,
7348
+ defaultHeartbeatTimeoutMs: RUNTIME_CLAIM_LEASE_SECONDS * 1000,
7349
+ afterRunIds: state.afterRunIds,
7350
+ },
7351
+ options,
7352
+ ),
7353
+ );
7354
+ outcome.stale = work.stale.length;
7355
+
7356
+ let budgetTimer: ReturnType<typeof setTimeout> | undefined;
7357
+ const budgetElapsed =
7358
+ input.clock?.budgetElapsed ??
7359
+ new Promise<void>((resolve) => {
7360
+ budgetTimer = setTimeout(
7361
+ resolve,
7362
+ ABSURD_DETACHED_RUNNER_LIVENESS_PASS_BUDGET_MS,
7363
+ );
7364
+ });
7365
+
7366
+ /** Never waits on a projection an earlier settlement left running. */
7367
+ const projectTerminal = async (
7368
+ settled: SettledRunnerTerminal,
7369
+ ): Promise<void> => {
7370
+ if (!input.projection) return;
7371
+ const { terminal } = settled;
7372
+ if (state.projecting) {
7373
+ // The run-ledger projector still delivers this terminal.
7374
+ console.warn(
7375
+ '[absurd-runtime.worker] detached_runner_liveness_projection_skipped',
7376
+ { runId: terminal.runId, attempt: terminal.attempt },
7377
+ );
7378
+ return;
7379
+ }
7380
+ state.projecting = true;
7381
+ const deadline = new AbortController();
7382
+ void budgetElapsed.then(() => deadline.abort());
7383
+ await projectRecoveredRunnerTerminal({
7384
+ deps: input.projection,
7385
+ launch: settled.launch,
7386
+ runId: terminal.runId,
7387
+ attempt: terminal.attempt,
7388
+ runnerResult: settled.runnerResult,
7389
+ options,
7390
+ signal: deadline.signal,
7391
+ }).finally(() => {
7392
+ state.projecting = false;
7393
+ });
7394
+ };
7395
+
7396
+ const releaseAfterTerminal = async (settled: SettledRunnerTerminal) => {
7397
+ const { terminal, launch, runnerResult, suspension } = settled;
7398
+ try {
7399
+ if (suspension) {
7400
+ reportDetachedTerminalToModalCanary({
7401
+ launch,
7402
+ runId: terminal.runId,
7403
+ attempt: terminal.attempt,
7404
+ policy: resolveLaunchRuntimeSandboxPlacementPolicy({
7405
+ runtimeBackend: normalizePlayRuntimeBackend(
7406
+ launch.runtimeBackend || 'daytona',
7407
+ ),
7408
+ runtimeSandboxPlacementPolicyId:
7409
+ launch.runtimeSandboxPlacementPolicyId,
7410
+ }),
7411
+ production: input.canaryProduction,
7412
+ suspension,
7413
+ runnerResult,
7414
+ });
7415
+ }
7416
+ } catch (error) {
7417
+ console.warn(
7418
+ '[absurd-runtime.worker] detached_runner_liveness_canary_failed',
7419
+ {
7420
+ runId: terminal.runId,
7421
+ error: error instanceof Error ? error.message : String(error),
7422
+ },
7423
+ );
7424
+ }
7425
+ // A customer database or the app can stall here, so this waits only for
7426
+ // the budget. Terminal truth is committed and an expired lease is the
7427
+ // crash path.
7428
+ await Promise.race([
7429
+ Promise.all([
7430
+ projectTerminal(settled),
7431
+ settled.schedulerStatus === 'failed'
7432
+ ? releaseFailedTerminalAttempt({
7433
+ runWithClient: input.withClient,
7434
+ launch,
7435
+ runId: terminal.runId,
7436
+ generation: terminal.attempt,
7437
+ options,
7438
+ })
7439
+ : undefined,
7440
+ ]),
7441
+ budgetElapsed,
7442
+ ]);
7443
+ };
7444
+
7445
+ type Settlement =
7446
+ | { kind: 'settled'; entries: SettledRunnerTerminal[] }
7447
+ | { kind: 'deferred'; reason: 'pass_ended' | 'store_busy' };
7448
+ // One at a time, across passes too: a settlement loads the launch, which
7449
+ // can be large, and a store that stalls one must not gain one per pass.
7450
+ let settlementTurn: Promise<unknown> = Promise.resolve();
7451
+ const settleTerminal = async (
7452
+ terminal: PostgresSchedulerUnsettledRunnerTerminal,
7453
+ ): Promise<void> => {
7454
+ const settle = settlementTurn.then(async (): Promise<Settlement> => {
7455
+ // Queued behind a stalled one, it never starts after its pass's budget.
7456
+ if (!active()) return { kind: 'deferred', reason: 'pass_ended' };
7457
+ if (state.settling) return { kind: 'deferred', reason: 'store_busy' };
7458
+ state.settling = true;
7459
+ try {
7460
+ const entries: SettledRunnerTerminal[] = [];
7461
+ await input.withClient(async (client) => {
7462
+ await recoverAbsurdRuntimeCompletedRunnerTerminals({
7463
+ client,
7464
+ // The run's immutable launch names its lane, so no lane list is
7465
+ // needed and a long-retired lane settles like a live one.
7466
+ queue: absurdQueueNameForRelease(
7467
+ absurdReleaseIdForLaunch({ absurdReleaseId: terminal.releaseId }),
7468
+ ),
7469
+ schedulerOptions: options,
7470
+ terminals: [
7471
+ {
7472
+ ...terminal,
7473
+ result: await readPostgresSchedulerRunnerTerminal(
7474
+ client,
7475
+ { runId: terminal.runId, attempt: terminal.attempt },
7476
+ options,
7477
+ ),
7478
+ },
7479
+ ],
7480
+ onTerminalSettled: (entry) => entries.push(entry),
7481
+ });
7482
+ });
7483
+ return { kind: 'settled', entries };
7484
+ } finally {
7485
+ state.settling = false;
7486
+ }
7487
+ });
7488
+ settlementTurn = settle.catch(() => undefined);
7489
+ const logFailure = (error: unknown, afterPass: boolean) =>
7490
+ console.error(
7491
+ '[absurd-runtime.worker] detached_runner_liveness_settlement_failed',
7492
+ {
7493
+ runId: terminal.runId,
7494
+ attempt: terminal.attempt,
7495
+ afterPass,
7496
+ error: error instanceof Error ? error.message : String(error),
7497
+ },
7498
+ );
7499
+ try {
7500
+ const settlement = await Promise.race([
7501
+ settle,
7502
+ budgetElapsed.then(() => 'budget_spent' as const),
7503
+ ]);
7504
+ if (settlement === 'budget_spent') {
7505
+ // It holds the store until it ends, and no settlement starts before.
7506
+ // A terminal it commits still releases its attempt and folds its
7507
+ // progress. The spent budget has already cancelled its outbox
7508
+ // request, so the run-ledger projector delivers that row.
7509
+ console.warn(
7510
+ '[absurd-runtime.worker] detached_runner_liveness_settlement_overran',
7511
+ { runId: terminal.runId, attempt: terminal.attempt },
7512
+ );
7513
+ void settle
7514
+ .then(async (late) => {
7515
+ if (late.kind !== 'settled') return;
7516
+ for (const entry of late.entries) await releaseAfterTerminal(entry);
7517
+ })
7518
+ .catch((error: unknown) => logFailure(error, true));
7519
+ return;
7520
+ }
7521
+ if (settlement.kind === 'deferred') {
7522
+ // The recorded terminal stays for the next pass or the parked attempt.
7523
+ if (settlement.reason === 'store_busy') {
7524
+ console.warn(
7525
+ '[absurd-runtime.worker] detached_runner_liveness_settlement_deferred',
7526
+ { runId: terminal.runId, attempt: terminal.attempt },
7527
+ );
7528
+ }
7529
+ return;
7530
+ }
7531
+ for (const entry of settlement.entries) {
7532
+ // A terminal another writer committed counts as theirs, but its
7533
+ // cleanup still runs here: the controller's lane recovery does none
7534
+ // of it, and a worker can die between its commit and its cleanup.
7535
+ // Each step is safe to repeat, as the lane recovery's projection is.
7536
+ if (entry.terminalizedByAnotherWorker) outcome.fenced += 1;
7537
+ else outcome.settled += 1;
7538
+ await releaseAfterTerminal(entry);
7539
+ }
7540
+ } catch (error) {
7541
+ // The terminal stays on the attempt, so the next pass retries it and the
7542
+ // parked attempt can still commit the same verdict.
7543
+ outcome.settlementFailed += 1;
7544
+ logFailure(error, false);
7545
+ }
7546
+ };
7547
+
7548
+ const inspectStaleRunner = async (
7549
+ candidate: PostgresSchedulerStaleDetachedRunner,
7550
+ ): Promise<void> => {
7551
+ const provider = candidate.provider;
7552
+ const recheckLater = () =>
7553
+ state.nextCheckAtMs.set(
7554
+ `${candidate.runId}:${candidate.attempt}`,
7555
+ now() + ABSURD_DETACHED_RUNNER_LIVENESS_RECHECK_MS,
7556
+ );
7557
+ const suspension = parseOverdueDetachedRunnerSuspension(
7558
+ candidate.suspension,
7559
+ );
7560
+ if (!suspension) {
7561
+ outcome.invalidSuspensions += 1;
7562
+ recheckLater();
7563
+ console.warn(
7564
+ '[absurd-runtime.worker] detached_runner_liveness_invalid_suspension',
7565
+ { runId: candidate.runId, attempt: candidate.attempt },
7566
+ );
7567
+ return;
7568
+ }
7569
+ if (
7570
+ state.abandonedReads[provider] >=
7571
+ ABSURD_DETACHED_RUNNER_LIVENESS_MAX_ABANDONED_READS
7572
+ ) {
7573
+ outcome.unreadable += 1;
7574
+ return;
7575
+ }
7576
+ outcome.inspected += 1;
7577
+ state.readsInFlight[provider] += 1;
7578
+ let slotHeld = true;
7579
+ let abandoned = false;
7580
+ const releaseSlot = () => {
7581
+ if (!slotHeld) return;
7582
+ slotHeld = false;
7583
+ state.readsInFlight[provider] -= 1;
7584
+ };
7585
+ const read = Promise.resolve()
7586
+ .then(() => inspect(suspension))
7587
+ .catch((error: unknown) => {
7588
+ console.warn(
7589
+ '[absurd-runtime.worker] detached_runner_liveness_evidence_failed',
7590
+ JSON.stringify({
7591
+ runId: candidate.runId,
7592
+ sandboxId: suspension.sandboxId,
7593
+ error: error instanceof Error ? error.message : String(error),
7594
+ }),
7595
+ );
7596
+ return null;
7597
+ })
7598
+ .finally(() => {
7599
+ if (abandoned) state.abandonedReads[provider] -= 1;
7600
+ releaseSlot();
7601
+ });
7602
+ void readDeadline().then(() => {
7603
+ if (!slotHeld) return;
7604
+ abandoned = true;
7605
+ state.abandonedReads[provider] += 1;
7606
+ releaseSlot();
7607
+ console.warn(
7608
+ '[absurd-runtime.worker] detached_runner_liveness_read_abandoned',
7609
+ JSON.stringify({
7610
+ runId: candidate.runId,
7611
+ sandboxId: suspension.sandboxId,
7612
+ provider,
7613
+ abandonedReads: state.abandonedReads[provider],
7614
+ }),
7615
+ );
7616
+ });
7617
+ const inspection = await Promise.race([
7618
+ read,
7619
+ budgetElapsed.then(() => null),
7620
+ ]);
7621
+ recheckLater();
7622
+ if (
7623
+ inspection &&
7624
+ !DETACHED_RUNNER_UNANSWERED_STAGES.has(inspection.diagnosis?.stage ?? '')
7625
+ ) {
7626
+ answeredReads[provider] += 1;
7627
+ } else {
7628
+ outcome.unreadable += 1;
7629
+ }
7630
+ if (!inspection || !detachedRunnerInspectionProvesGone(inspection)) {
7631
+ outcome.unproven += 1;
7632
+ console.warn(
7633
+ '[absurd-runtime.worker] detached_runner_liveness_unproven',
7634
+ JSON.stringify({
7635
+ runId: candidate.runId,
7636
+ runnerAttempt: candidate.attempt,
7637
+ sandboxId: suspension.sandboxId,
7638
+ diagnosis: inspection?.diagnosis?.stage ?? 'unavailable',
7639
+ }),
7640
+ );
7641
+ return;
7642
+ }
7643
+ outcome.proven += 1;
7644
+ if (input.mode === 'observe') {
7645
+ outcome.wouldSettle += 1;
7646
+ console.info(
7647
+ '[absurd-runtime.worker] detached_runner_liveness_would_settle',
7648
+ JSON.stringify({
7649
+ runId: candidate.runId,
7650
+ runnerAttempt: candidate.attempt,
7651
+ sandboxId: suspension.sandboxId,
7652
+ diagnosis: inspection.diagnosis?.stage ?? 'unknown',
7653
+ }),
7654
+ );
7655
+ return;
7656
+ }
7657
+ const observedAtMs = now();
7658
+ const heartbeatAtMs = candidate.heartbeatAtMs ?? Number.NaN;
7659
+ const heartbeatAgeMs = Number.isFinite(heartbeatAtMs)
7660
+ ? Math.max(0, observedAtMs - heartbeatAtMs)
7661
+ : null;
7662
+ const verdict: PlayRunnerResult =
7663
+ inspection.result && inspection.result.status !== 'suspended'
7664
+ ? inspection.result
7665
+ : detachedRunnerLostResult({
7666
+ suspension,
7667
+ observedAtMs,
7668
+ livenessStatus: 'waiting',
7669
+ heartbeatAtMs,
7670
+ heartbeatAgeMs,
7671
+ inspectionAttempted: true,
7672
+ inspection,
7673
+ inspectionFailed: false,
7674
+ });
7675
+ const logVerdictFailure = (error: unknown, afterPass: boolean) =>
7676
+ console.error(
7677
+ '[absurd-runtime.worker] detached_runner_liveness_verdict_failed',
7678
+ {
7679
+ runId: candidate.runId,
7680
+ attempt: candidate.attempt,
7681
+ afterPass,
7682
+ error: error instanceof Error ? error.message : String(error),
7683
+ },
7684
+ );
7685
+ const reportExpired = () => {
7686
+ if (verdict.status !== 'failed' || verdict === inspection.result) return;
7687
+ reportDetachedRunnerLivenessExpired({
7688
+ runId: candidate.runId,
7689
+ suspension,
7690
+ observedAtMs,
7691
+ liveness: { heartbeatAt: candidate.heartbeatAt, status: 'waiting' },
7692
+ heartbeatAgeMs,
7693
+ });
7694
+ };
7695
+ const write = input.withClient((client) =>
7696
+ recordPostgresSchedulerDetachedRunnerLivenessVerdict(
7697
+ client,
7698
+ {
7699
+ runId: candidate.runId,
7700
+ attempt: candidate.attempt,
7701
+ leaseToken: candidate.leaseToken,
7702
+ observedHeartbeatAt: candidate.heartbeatAt,
7703
+ // Fallback can leave `runtimeBackend` naming a provider that never
7704
+ // ran this sandbox, so record the one that did.
7705
+ result: {
7706
+ ...verdict,
7707
+ stats: { ...verdict.stats, sandboxProvider: provider },
7708
+ },
7709
+ },
7710
+ options,
7711
+ ),
7712
+ );
7713
+ try {
7714
+ const recorded = await Promise.race([
7715
+ write,
7716
+ budgetElapsed.then(() => 'budget_spent' as const),
7717
+ ]);
7718
+ if (recorded === 'budget_spent') {
7719
+ // If it commits, the next pass settles it like any recorded terminal.
7720
+ // The scheduler pool's acquire and query timeouts end it otherwise.
7721
+ console.warn(
7722
+ '[absurd-runtime.worker] detached_runner_liveness_verdict_overran',
7723
+ { runId: candidate.runId, attempt: candidate.attempt },
7724
+ );
7725
+ void write
7726
+ .then((late) => {
7727
+ if (late) reportExpired();
7728
+ })
7729
+ .catch((error: unknown) => logVerdictFailure(error, true));
7730
+ return;
7731
+ }
7732
+ if (!recorded) {
7733
+ outcome.fenced += 1;
7734
+ return;
7735
+ }
7736
+ } catch (error) {
7737
+ outcome.settlementFailed += 1;
7738
+ logVerdictFailure(error, false);
7739
+ return;
7740
+ }
7741
+ reportExpired();
7742
+ await settleTerminal(candidate);
7743
+ };
7744
+
7745
+ try {
7746
+ await Promise.all([
7747
+ input.mode === 'enforce'
7748
+ ? runDetachedRunnerLivenessLane({
7749
+ items: work.terminals,
7750
+ concurrency: 1,
7751
+ active,
7752
+ visit: settleTerminal,
7753
+ }).then((visited) => {
7754
+ outcome.recorded = visited;
7755
+ })
7756
+ : null,
7757
+ ...ABSURD_DETACHED_RUNNER_LIVENESS_PROVIDERS.map(async (provider) => {
7758
+ const page = work.stale.filter(
7759
+ (candidate) => candidate.provider === provider,
7760
+ );
7761
+ const due = page.filter(
7762
+ (candidate) =>
7763
+ !state.nextCheckAtMs.has(`${candidate.runId}:${candidate.attempt}`),
7764
+ );
7765
+ outcome.notDue += page.length - due.length;
7766
+ const visited = await runDetachedRunnerLivenessLane({
7767
+ items: due,
7768
+ concurrency: Math.max(0, concurrency - state.readsInFlight[provider]),
7769
+ active,
7770
+ visit: inspectStaleRunner,
7771
+ });
7772
+ if (due.length > 0 && answeredReads[provider] === 0) {
7773
+ outcome.blindProviders.push(provider);
7774
+ }
7775
+ // Walk the set rather than rereading its head, so a stale set larger
7776
+ // than a page still reaches every sandbox. A short page wraps.
7777
+ if (visited < due.length) {
7778
+ if (visited > 0) {
7779
+ state.afterRunIds[provider] = due[visited - 1]!.runId;
7780
+ }
7781
+ } else {
7782
+ state.afterRunIds[provider] =
7783
+ page.length < limit ? null : (page.at(-1)?.runId ?? null);
7784
+ }
7785
+ }),
7786
+ ]);
7787
+ } finally {
7788
+ if (budgetTimer) clearTimeout(budgetTimer);
7789
+ }
7790
+ outcome.budgetExhausted = now() >= deadlineAtMs;
7791
+ return outcome;
7792
+ }
7793
+
6738
7794
  /**
6739
7795
  * Non-terminal task states on a lane queue's `absurd.t_<queue>` table. A queue
6740
7796
  * with zero of these has no runnable/parked work left — the lane is drainable.
@@ -7006,12 +8062,16 @@ export async function runAbsurdLaneAdoptionSupervisor(input: {
7006
8062
  startAdoptionWorker: (queue: string) => Promise<AbsurdAdoptedLaneWorker>;
7007
8063
  intervalMs?: number;
7008
8064
  maxQueues?: number;
7009
- sleep?: (ms: number) => Promise<void>;
8065
+ sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
8066
+ signal?: AbortSignal;
7010
8067
  shouldStop?: () => boolean;
7011
8068
  onAttached?: (queues: readonly string[]) => void;
7012
8069
  onError?: (error: unknown) => void;
7013
8070
  }): Promise<void> {
7014
- const wait = input.sleep ?? ((ms: number) => sleep(ms));
8071
+ const wait =
8072
+ input.sleep ??
8073
+ ((ms: number, signal?: AbortSignal) => sleep(ms, undefined, { signal }));
8074
+ const stopped = () => input.signal?.aborted || input.shouldStop?.();
7015
8075
  const intervalMs =
7016
8076
  input.intervalMs ?? ABSURD_LANE_ADOPTION_DISCOVERY_INTERVAL_MS;
7017
8077
  const maxQueues = input.maxQueues ?? ABSURD_LANE_ADOPTION_MAX_QUEUES;
@@ -7045,9 +8105,10 @@ export async function runAbsurdLaneAdoptionSupervisor(input: {
7045
8105
  };
7046
8106
  try {
7047
8107
  for (;;) {
7048
- if (input.shouldStop?.()) return;
8108
+ if (stopped()) return;
7049
8109
  try {
7050
8110
  const adoptableReleaseIds = await input.discoverAdoptableReleaseIds();
8111
+ if (stopped()) return;
7051
8112
  const allQueues = absurdAdoptionQueuesForReleases({
7052
8113
  workerReleaseId: input.workerReleaseId,
7053
8114
  adoptableReleaseIds,
@@ -7076,10 +8137,12 @@ export async function runAbsurdLaneAdoptionSupervisor(input: {
7076
8137
  console.info('[absurd-runtime.worker] adoption_detached', { queue });
7077
8138
  }
7078
8139
  for (const queue of wanted) {
8140
+ if (stopped()) return;
7079
8141
  if (attached.has(queue) || draining.has(queue)) continue;
7080
8142
  if (attached.size + draining.size >= maxQueues) break;
7081
8143
  const worker = await input.startAdoptionWorker(queue);
7082
8144
  attached.set(queue, worker);
8145
+ if (stopped()) return;
7083
8146
  console.info('[absurd-runtime.worker] adoption_attached', {
7084
8147
  queue,
7085
8148
  workerReleaseId: input.workerReleaseId,
@@ -7105,8 +8168,18 @@ export async function runAbsurdLaneAdoptionSupervisor(input: {
7105
8168
  error: error instanceof Error ? error.message : String(error),
7106
8169
  });
7107
8170
  }
7108
- if (input.shouldStop?.()) return;
7109
- await wait(intervalMs);
8171
+ if (stopped()) return;
8172
+ try {
8173
+ await wait(intervalMs, input.signal);
8174
+ } catch (error) {
8175
+ if (
8176
+ input.signal?.aborted &&
8177
+ error instanceof Error &&
8178
+ error.name === 'AbortError'
8179
+ )
8180
+ return;
8181
+ throw error;
8182
+ }
7110
8183
  }
7111
8184
  } finally {
7112
8185
  await closeAll();
@@ -7680,6 +8753,9 @@ async function processPostgresSchedulerSandboxCleanupJob(input: {
7680
8753
  cleanupAttempt: job.attemptCount,
7681
8754
  failureCount: job.failureCount,
7682
8755
  } as const;
8756
+ const deleteProof: {
8757
+ acceptance: PostgresSchedulerSandboxDeleteAcceptance | null;
8758
+ } = { acceptance: null };
7683
8759
  const cleanup = createSandboxCleanupJob({
7684
8760
  store: createPostgresSandboxCleanupStore({
7685
8761
  runWithClient: input.runWithClient,
@@ -7692,7 +8768,27 @@ async function processPostgresSchedulerSandboxCleanupJob(input: {
7692
8768
  timeoutMs,
7693
8769
  }): Promise<SandboxCleanupDeleteOutcome> => {
7694
8770
  try {
7695
- await recordCleanupEvent('provider_delete_started', { timeoutMs });
8771
+ // Only a job claimed before can have an earlier accepted delete. A
8772
+ // failed read retries, so a 404 is never judged without this proof.
8773
+ deleteProof.acceptance =
8774
+ resource.provider === 'daytona' && resource.cleanupAttempt > 1
8775
+ ? await input.runWithClient((client) =>
8776
+ readPostgresSchedulerSandboxDeleteAcceptance(
8777
+ client,
8778
+ {
8779
+ provider: resource.provider,
8780
+ sandboxId: resource.sandboxId,
8781
+ routingDomain: resource.routingDomain,
8782
+ runId: resource.runId,
8783
+ attempt: resource.attempt,
8784
+ },
8785
+ input.schedulerOptions,
8786
+ ),
8787
+ )
8788
+ : null;
8789
+ if (!deleteProof.acceptance) {
8790
+ await recordCleanupEvent('provider_delete_started', { timeoutMs });
8791
+ }
7696
8792
  const providerOutcome = await deleteRuntimeSandboxBounded({
7697
8793
  deleteRuntimeSandbox: async (sandboxId) => {
7698
8794
  if (resource.provider === 'modal') {
@@ -7728,6 +8824,7 @@ async function processPostgresSchedulerSandboxCleanupJob(input: {
7728
8824
  return await input.deleteRuntimeSandboxWithOutcome(
7729
8825
  sandboxId,
7730
8826
  resource.routingDomain,
8827
+ deleteProof.acceptance,
7731
8828
  );
7732
8829
  }
7733
8830
  return (await input.deleteRuntimeSandbox(sandboxId))
@@ -7754,6 +8851,24 @@ async function processPostgresSchedulerSandboxCleanupJob(input: {
7754
8851
  routingDomain: providerOutcome.organizationId,
7755
8852
  };
7756
8853
  }
8854
+ if (providerOutcome.kind === 'delete_pending') {
8855
+ // The first acceptance is durable proof for the read-back, and the
8856
+ // moment billing keeps as this sandbox's end.
8857
+ if (providerOutcome.deleteAccepted && !deleteProof.acceptance) {
8858
+ await recordCleanupEvent('provider_delete_accepted', {
8859
+ routingDomain: providerOutcome.organizationId,
8860
+ });
8861
+ } else {
8862
+ await recordCleanupEvent('provider_delete_pending', {
8863
+ code: providerOutcome.code,
8864
+ });
8865
+ }
8866
+ return {
8867
+ kind: 'pending',
8868
+ code: providerOutcome.code,
8869
+ detail: `${providerOutcome.code}: ${providerOutcome.detail}`,
8870
+ };
8871
+ }
7757
8872
  await recordCleanupEvent('provider_delete_failed', {
7758
8873
  code:
7759
8874
  'code' in providerOutcome
@@ -7815,6 +8930,15 @@ async function processPostgresSchedulerSandboxCleanupJob(input: {
7815
8930
  cleanupAuthority: 'durable_cleanup_claim',
7816
8931
  providerOutcome: outcome.providerOutcome,
7817
8932
  completionOutcome: outcome.completion,
8933
+ // An upper bound: the 404 was seen on a paced read, not as it happened.
8934
+ ...(deleteProof.acceptance
8935
+ ? {
8936
+ disappearedAfterMs: Math.max(
8937
+ 0,
8938
+ Date.now() - deleteProof.acceptance.acceptedAtMs,
8939
+ ),
8940
+ }
8941
+ : {}),
7818
8942
  });
7819
8943
  return { deleted: 1, retried: 0 };
7820
8944
  }
@@ -8000,7 +9124,7 @@ export async function reconcilePostgresSchedulerAmbiguousModalCreatesOnce(input:
8000
9124
  return { observed: intents.length, resourcesRecorded, resolvedAbsent };
8001
9125
  }
8002
9126
 
8003
- async function reconcileReservedDaytonaCancellationOnce(input: {
9127
+ export async function reconcileReservedDaytonaCancellationOnce(input: {
8004
9128
  runWithClient: AbsurdRuntimeWorkerDeps['runWithClient'];
8005
9129
  environment: 'preview' | 'production';
8006
9130
  schedulerOptions: Pick<PostgresSchedulerOptions, 'schema'>;
@@ -8037,7 +9161,11 @@ async function reconcileReservedDaytonaCancellationOnce(input: {
8037
9161
  await input.runWithClient((client) =>
8038
9162
  abandonRuntimeSandboxOperationReservation(
8039
9163
  client,
8040
- { operationKey: operation.operationKey },
9164
+ {
9165
+ operationKey: operation.operationKey,
9166
+ // Execution recovery ending is not proof a sent create cannot land.
9167
+ onlyWhileRunActive: true,
9168
+ },
8041
9169
  input.schedulerOptions,
8042
9170
  ),
8043
9171
  );
@@ -8045,27 +9173,75 @@ async function reconcileReservedDaytonaCancellationOnce(input: {
8045
9173
  } finally {
8046
9174
  control.dispose();
8047
9175
  }
8048
- if (sandbox.labels?.operationKey !== operation.providerOperationToken) {
9176
+ await persistReservedDaytonaCancellationCleanupIntent({
9177
+ operation,
9178
+ sandbox,
9179
+ configuredOrganizationId: process.env.DAYTONA_ORGANIZATION_ID?.trim(),
9180
+ recordCleanupIntent: (identity) =>
9181
+ input.runWithClient((client) =>
9182
+ recordDaytonaSandboxCleanupIntent(
9183
+ client,
9184
+ identity,
9185
+ input.schedulerOptions,
9186
+ ),
9187
+ ),
9188
+ });
9189
+ }
9190
+
9191
+ export async function persistReservedDaytonaCancellationCleanupIntent(input: {
9192
+ operation: {
9193
+ operationKey: string;
9194
+ providerOperationToken: string;
9195
+ orgId: string;
9196
+ runId: string;
9197
+ };
9198
+ sandbox: {
9199
+ id: string;
9200
+ name?: string | null;
9201
+ organizationId?: string | null;
9202
+ labels?: Record<string, string | undefined> | null;
9203
+ };
9204
+ configuredOrganizationId?: string | null;
9205
+ recordCleanupIntent: (identity: {
9206
+ operationKey: string;
9207
+ providerOperationToken: string;
9208
+ orgId: string;
9209
+ runId: string;
9210
+ sandboxId: string;
9211
+ routingDomain: string;
9212
+ }) => Promise<boolean>;
9213
+ }): Promise<void> {
9214
+ const sandboxName = `dl-${input.operation.providerOperationToken}`;
9215
+ if (
9216
+ !daytonaSandboxMatchesOperationIdentity({
9217
+ sandbox: input.sandbox,
9218
+ sandboxName,
9219
+ providerOperationToken: input.operation.providerOperationToken,
9220
+ orgId: input.operation.orgId,
9221
+ runId: input.operation.runId,
9222
+ configuredOrganizationId: input.configuredOrganizationId,
9223
+ })
9224
+ ) {
8049
9225
  throw new Error(
8050
- `Daytona cancellation lookup ${sandboxName} lacks its exact operation label.`,
9226
+ `Daytona cancellation lookup ${sandboxName} has a mismatched sandbox identity or organization routing; cleanup suppressed.`,
8051
9227
  );
8052
9228
  }
8053
- if (!sandbox.organizationId) {
9229
+ const routingDomain = input.sandbox.organizationId?.trim();
9230
+ if (!routingDomain) {
8054
9231
  throw new Error(
8055
- `Daytona cancellation lookup ${sandboxName} lacks its routing organization.`,
9232
+ `Daytona cancellation lookup ${sandboxName} lacks its routing organization; cleanup suppressed.`,
9233
+ );
9234
+ }
9235
+ const recorded = await input.recordCleanupIntent({
9236
+ ...input.operation,
9237
+ sandboxId: input.sandbox.id,
9238
+ routingDomain,
9239
+ });
9240
+ if (!recorded) {
9241
+ throw new Error(
9242
+ `Daytona cancellation cleanup intent for ${sandboxName} was not durably recorded.`,
8056
9243
  );
8057
9244
  }
8058
- await input.runWithClient((client) =>
8059
- recordRecoveredDaytonaCancellationSandbox(
8060
- client,
8061
- {
8062
- operationKey: operation.operationKey,
8063
- sandboxId: sandbox.id,
8064
- routingDomain: sandbox.organizationId,
8065
- },
8066
- input.schedulerOptions,
8067
- ),
8068
- );
8069
9245
  }
8070
9246
 
8071
9247
  export async function runPostgresSchedulerSandboxCleanupController(input: {
@@ -8773,7 +9949,7 @@ export async function startRuntimeWorkerHost(
8773
9949
  // claiming and still hold parked durable waits, so a retired lane does not
8774
9950
  // have to stay alive until its last sleeping run wakes. A draining lane
8775
9951
  // attaches nothing — it is on its way out itself.
8776
- let adoptionStopped = false;
9952
+ const adoptionStop = new AbortController();
8777
9953
  let adoptionSupervisor: Promise<void> | null = null;
8778
9954
  if (!drainExit) {
8779
9955
  const releaseEnvironment =
@@ -8782,7 +9958,7 @@ export async function startRuntimeWorkerHost(
8782
9958
  : ('production' as const);
8783
9959
  adoptionSupervisor = runAbsurdLaneAdoptionSupervisor({
8784
9960
  workerReleaseId: releaseId,
8785
- shouldStop: () => adoptionStopped,
9961
+ signal: adoptionStop.signal,
8786
9962
  discoverAdoptableReleaseIds: async () => {
8787
9963
  const lanes = await withRuntimeSchedulerClient(poolOptions, (client) =>
8788
9964
  listPostgresSchedulerAdoptableReleaseLanes(
@@ -8903,7 +10079,7 @@ export async function startRuntimeWorkerHost(
8903
10079
  drainStopped = true;
8904
10080
  billingStopped = true;
8905
10081
  cleanupStopped = true;
8906
- adoptionStopped = true;
10082
+ adoptionStop.abort();
8907
10083
  overdueCeilingStopped = true;
8908
10084
  },
8909
10085
  controllers: [