deepline 0.3.392 → 0.3.394

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +156 -24
  2. package/dist/bundling-sources/sdk/src/compat.ts +9 -0
  3. package/dist/bundling-sources/sdk/src/http.ts +5 -1
  4. package/dist/bundling-sources/sdk/src/index.ts +5 -0
  5. package/dist/bundling-sources/sdk/src/plays/run-request.ts +182 -6
  6. package/dist/bundling-sources/sdk/src/release.ts +4 -1
  7. package/dist/bundling-sources/sdk/src/types.ts +13 -0
  8. package/dist/bundling-sources/shared_libs/integration-contracts/request-warnings.ts +64 -0
  9. package/dist/bundling-sources/shared_libs/play-data-plane/runtime-sheet-storage-contract.ts +315 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +41 -8
  11. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +895 -54
  12. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +41 -4
  13. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-policy.ts +28 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/durable-invocation-fence.ts +36 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/freshness-receipt-execution.ts +251 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +11 -3
  17. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +1 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +30 -1
  19. package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +26 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +168 -10
  21. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +252 -10
  22. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +92 -58
  23. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +7 -1
  24. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +30 -7
  25. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1161 -316
  26. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +28 -15
  27. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +24 -3
  28. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +32 -0
  29. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +103 -23
  30. package/dist/bundling-sources/shared_libs/plays/core.ts +1 -1
  31. package/dist/bundling-sources/shared_libs/plays/dataset.ts +172 -79
  32. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +173 -1
  33. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +18 -0
  34. package/dist/cli/auth-IHKISLJQ.js +39 -0
  35. package/dist/cli/{auth-6JSA2ZCW.mjs → auth-OSXDAVCF.mjs} +4 -4
  36. package/dist/cli/{billing-Q5BLQHIU.js → billing-EKLYOXUY.js} +101 -74
  37. package/dist/cli/{billing-MRRO643H.mjs → billing-ZNNKX3UR.mjs} +32 -5
  38. package/dist/cli/{chunk-4SFPVW7A.js → chunk-2MDDA7A4.js} +26 -26
  39. package/dist/cli/{chunk-JOFPQGXW.js → chunk-354JCXA2.js} +50 -50
  40. package/dist/cli/{chunk-7E66A24V.mjs → chunk-3HU47EGZ.mjs} +71 -9
  41. package/dist/cli/{chunk-DSWSEHRX.mjs → chunk-3HWALOL2.mjs} +1 -1
  42. package/dist/cli/{chunk-T7OKJ2T6.js → chunk-4P27AXEA.js} +73 -63
  43. package/dist/cli/{chunk-24VEN5PQ.mjs → chunk-5BB2RGAX.mjs} +190 -18
  44. package/dist/cli/{chunk-JSLPHFP7.js → chunk-6YAUMG27.js} +3 -2
  45. package/dist/cli/{chunk-MU6M3URX.mjs → chunk-BP33SIZF.mjs} +1 -1
  46. package/dist/cli/{chunk-WMS5U6YB.js → chunk-CS6RAIVY.js} +20 -19
  47. package/dist/cli/{chunk-CVLM3WMJ.js → chunk-E23BQ5SC.js} +242 -183
  48. package/dist/cli/{chunk-2GAHHSLB.mjs → chunk-FJGW5GXU.mjs} +3 -2
  49. package/dist/cli/{chunk-YN6YA7ZZ.mjs → chunk-JHIJIHFB.mjs} +79 -20
  50. package/dist/cli/{chunk-Q6EOHNRH.mjs → chunk-JI2B4RZP.mjs} +119 -6
  51. package/dist/cli/{chunk-5O4B6AUN.js → chunk-KB4SDX5S.js} +71 -9
  52. package/dist/cli/{chunk-RV6ALZIX.js → chunk-KGNTKMRG.js} +32 -7
  53. package/dist/cli/{chunk-6DBOU7UH.js → chunk-KI635JN4.js} +176 -87
  54. package/dist/cli/{chunk-2FQG2HU4.js → chunk-KIUMEOM5.js} +8 -8
  55. package/dist/cli/{chunk-Y6VOGHF2.mjs → chunk-KZZC33PY.mjs} +0 -181
  56. package/dist/cli/{chunk-2Q3GEFL4.mjs → chunk-M57J2SK4.mjs} +2 -2
  57. package/dist/cli/{chunk-R3O2CZ4H.mjs → chunk-MFB2VKBO.mjs} +2 -2
  58. package/dist/cli/{chunk-QGKPTBRR.js → chunk-MH73HMPV.js} +3 -3
  59. package/dist/cli/{chunk-BB3FNYFY.js → chunk-MLPU5SMZ.js} +2 -2
  60. package/dist/cli/{chunk-2G7DXFHI.mjs → chunk-NR3DFKNF.mjs} +27 -2
  61. package/dist/cli/{chunk-EY7RFC6A.js → chunk-QIQBYKLB.js} +15 -15
  62. package/dist/cli/{chunk-RIV2GS5E.js → chunk-RBJYWRCF.js} +638 -466
  63. package/dist/cli/{chunk-3VN4FTE3.js → chunk-RDXFGPJU.js} +1 -182
  64. package/dist/cli/{chunk-YAZCDBXX.mjs → chunk-TJI2EN6K.mjs} +3 -2
  65. package/dist/cli/chunk-UBKA33KY.mjs +310 -0
  66. package/dist/cli/{chunk-PAIMOGCM.js → chunk-UHNDVD3P.js} +125 -12
  67. package/dist/cli/{chunk-4TCT77UF.js → chunk-URWF7NGQ.js} +6 -1
  68. package/dist/cli/{chunk-2QLML6G4.mjs → chunk-UXFSWG3Q.mjs} +4 -4
  69. package/dist/cli/{chunk-UCKJQG25.js → chunk-VK6FYZLP.js} +8 -8
  70. package/dist/cli/{chunk-ZTACINO5.mjs → chunk-X63FDZJZ.mjs} +13 -3
  71. package/dist/cli/chunk-XDVT3GOM.js +310 -0
  72. package/dist/cli/{chunk-A3APGZFP.mjs → chunk-XRQ2AQL7.mjs} +2 -2
  73. package/dist/cli/{chunk-3FV4GBRK.mjs → chunk-XXUDVEXK.mjs} +97 -8
  74. package/dist/cli/{chunk-PQSA443V.mjs → chunk-ZI2ZNDJK.mjs} +6 -1
  75. package/dist/cli/{chunk-HGYBEAQP.mjs → chunk-ZYBGA4AR.mjs} +3 -3
  76. package/dist/cli/command-docs.js +2 -2
  77. package/dist/cli/command-docs.mjs +1 -1
  78. package/dist/cli/{csv-E4QGUIGI.mjs → csv-5AXRB35H.mjs} +3 -3
  79. package/dist/cli/{csv-26U5T24Z.js → csv-LU7O75WE.js} +12 -12
  80. package/dist/cli/{db-JTRJIAQW.mjs → db-7XP5UWEW.mjs} +6 -5
  81. package/dist/cli/{db-QHBFLMVL.js → db-F7YTFKVM.js} +31 -30
  82. package/dist/cli/{describe-FDV2V74F.js → describe-FRH32RDV.js} +24 -23
  83. package/dist/cli/{describe-E3DE3ZK3.mjs → describe-K4ES5LIL.mjs} +17 -16
  84. package/dist/cli/{executions-B3GTNPRE.mjs → executions-IEHEHJUO.mjs} +6 -5
  85. package/dist/cli/{executions-VZYOGVQY.js → executions-RZV5XPT2.js} +8 -7
  86. package/dist/cli/{feedback-CR55FJA2.js → feedback-3P65QTX3.js} +7 -7
  87. package/dist/cli/{feedback-4CEBR3OV.mjs → feedback-FPUVZBAN.mjs} +3 -3
  88. package/dist/cli/{git-NM2PHHZ4.mjs → git-GLVVP7TY.mjs} +4 -4
  89. package/dist/cli/git-MFFAWVVX.js +23 -0
  90. package/dist/cli/index.js +100 -96
  91. package/dist/cli/index.mjs +47 -43
  92. package/dist/cli/{legacy-noops-A7MSW5ND.mjs → legacy-noops-3MOI6NBM.mjs} +4 -4
  93. package/dist/cli/{legacy-noops-ZUA4YFIH.js → legacy-noops-XKSGZR7Q.js} +7 -7
  94. package/dist/cli/{monitors-NVPRQYP3.mjs → monitors-FTBGNEFG.mjs} +6 -5
  95. package/dist/cli/{monitors-ZE3TLZ4H.js → monitors-NZ2DYNST.js} +87 -86
  96. package/dist/cli/{org-U7A6DV5D.js → org-MCKDQXIV.js} +66 -65
  97. package/dist/cli/{org-KUBILNQV.mjs → org-V2J5RBCN.mjs} +6 -5
  98. package/dist/cli/{play-HRDXQ4QE.mjs → play-AJFGME3B.mjs} +11 -10
  99. package/dist/cli/play-WLSN2EO2.js +158 -0
  100. package/dist/cli/{providers-VD6YTVQZ.js → providers-2JEZ2264.js} +9 -8
  101. package/dist/cli/{providers-OHIWI453.mjs → providers-I6RQJZFZ.mjs} +6 -5
  102. package/dist/cli/{quickstart-UBV2FONF.mjs → quickstart-BIDQDMO4.mjs} +3 -3
  103. package/dist/cli/{quickstart-ETTCWPA4.js → quickstart-GJ2RPDU3.js} +5 -5
  104. package/dist/cli/run-identity-G257GQED.js +25 -0
  105. package/dist/cli/run-identity-MRNCEGV2.mjs +25 -0
  106. package/dist/cli/search-5VN4YD4I.js +27 -0
  107. package/dist/cli/{search-F44IZRT7.mjs → search-ELRXFWG5.mjs} +7 -6
  108. package/dist/cli/{secrets-CAEMRORF.js → secrets-53IT6VXD.js} +15 -14
  109. package/dist/cli/{secrets-R6XOJZKU.mjs → secrets-SQXGZXIC.mjs} +6 -5
  110. package/dist/cli/{sessions-RLN6UQS3.mjs → sessions-2X5QF7SL.mjs} +4 -4
  111. package/dist/cli/{sessions-6GC7OFTT.js → sessions-CAV5M77Y.js} +5 -5
  112. package/dist/cli/{settings-DPNVUAV2.js → settings-532WNAZO.js} +76 -75
  113. package/dist/cli/{settings-KIPAMRPP.mjs → settings-7P67VGCJ.mjs} +9 -8
  114. package/dist/cli/{setup-DTL3L5SG.js → setup-6L2VSUWJ.js} +41 -41
  115. package/dist/cli/{setup-TU2F7PU5.mjs → setup-QGGBV2LE.mjs} +6 -6
  116. package/dist/cli/{skills-UV5MYJ46.mjs → skills-3FE62WBG.mjs} +4 -4
  117. package/dist/cli/skills-S6NPZAN7.js +28 -0
  118. package/dist/cli/tools-647RLXQS.js +41 -0
  119. package/dist/cli/{tools-QRRNDCQ7.mjs → tools-DMR25DEF.mjs} +10 -9
  120. package/dist/cli/{update-4426TF35.mjs → update-6RZMHJZP.mjs} +6 -6
  121. package/dist/cli/update-WVTSC3EM.js +28 -0
  122. package/dist/cli/{workflow-N4U5VLZ3.mjs → workflow-L2DQRE6R.mjs} +7 -6
  123. package/dist/cli/{workflow-HNLOVISB.js → workflow-TX4F2PIX.js} +17 -16
  124. package/dist/{client-e6Qh5ZIg.d.mts → client-BktIPdIj.d.mts} +153 -4
  125. package/dist/{client-Uxuy-xQ2.d.ts → client-C3O1Ipf3.d.ts} +153 -4
  126. package/dist/{compiler-manifest-CY2v8ab0.d.ts → compiler-manifest-DLxUY5kI.d.mts} +124 -10
  127. package/dist/{compiler-manifest-CY2v8ab0.d.mts → compiler-manifest-DLxUY5kI.d.ts} +124 -10
  128. package/dist/{index-BZ6uzZJl.d.ts → index-B_GM9E3q.d.ts} +1 -1
  129. package/dist/{index-B9fBZH1g.d.mts → index-SCb0NvDb.d.mts} +1 -1
  130. package/dist/index.d.mts +5 -5
  131. package/dist/index.d.ts +5 -5
  132. package/dist/index.js +120 -29
  133. package/dist/index.mjs +111 -20
  134. package/dist/install-integrity.json +94 -88
  135. package/dist/plays/bundle-play-file.d.mts +3 -3
  136. package/dist/plays/bundle-play-file.d.ts +3 -3
  137. package/dist/plays/bundle-play-file.mjs +2 -2
  138. package/dist/plays/run-request.d.mts +2 -2
  139. package/dist/plays/run-request.d.ts +2 -2
  140. package/dist/plays/run-request.js +5 -2
  141. package/dist/plays/run-request.mjs +4 -1
  142. package/dist/release.d.mts +4 -1
  143. package/dist/release.d.ts +4 -1
  144. package/dist/release.js +2 -2
  145. package/dist/release.mjs +1 -1
  146. package/package.json +1 -1
  147. package/dist/cli/auth-67TAELQL.js +0 -39
  148. package/dist/cli/git-I5JTK6YU.js +0 -23
  149. package/dist/cli/play-A2V7YDU7.js +0 -157
  150. package/dist/cli/search-WWXMBVPH.js +0 -26
  151. package/dist/cli/skills-SWOSEZLB.js +0 -28
  152. package/dist/cli/tools-IL2YCDQ6.js +0 -40
  153. package/dist/cli/update-BERDPEYR.js +0 -28
@@ -178,6 +178,9 @@ import {
178
178
  readPostgresSchedulerRunnerLiveness,
179
179
  readPostgresSchedulerRuntimeResourcesForRun,
180
180
  isPostgresSchedulerRunnerProgressAttemptCurrent,
181
+ appendPostgresRunLedgerEvents,
182
+ readPostgresSchedulerRunnerTerminalReceivedAtMs,
183
+ claimPostgresSchedulerWaitingSuspensionAttemptForFailure,
181
184
  requestComputeBillingDispatchToken,
182
185
  blockPostgresSchedulerCronSchedule,
183
186
  PostgresSchedulerCronScheduleConfigurationError,
@@ -233,6 +236,7 @@ import {
233
236
  import { runWithAbsurdSchedulerQuerySignal } from './absurd-pool';
234
237
  import {
235
238
  buildFailedTerminalSettledProgressEvents,
239
+ collectGatewayRunnerTerminalProgressLedgerEvents,
236
240
  createPostgresSchedulerProgressReporter,
237
241
  type PostgresSchedulerProgressReporter,
238
242
  } from './postgres-progress';
@@ -3261,11 +3265,57 @@ async function executeAssignedAbsurdAttempt(input: {
3261
3265
  terminalLogProducerAttempt: input.terminalLogProducerAttempt,
3262
3266
  }),
3263
3267
  });
3264
- // A real suspension closes an invocation's counter segment before its
3265
- // wait or handoff. Detached terminal recovery can replay the retained
3266
- // result and its idempotent final segment after a crash. A detached_runner
3267
- // placeholder still owns a live runner; preserve its park-first recovery.
3268
- if (suspension.kind !== 'detached_runner') {
3268
+ const retainedDetachedSuspension =
3269
+ input.fromDetachedRunner && suspension.kind !== 'detached_runner';
3270
+ let finalProgressRetained = false;
3271
+ if (retainedDetachedSuspension) {
3272
+ await runWithAbsurdExecutionLeaseHeartbeat({
3273
+ ctx,
3274
+ runId,
3275
+ operation: () =>
3276
+ deps.runWithClient(async (client) => {
3277
+ const runnerAttempt =
3278
+ input.terminalLogProducerAttempt ?? generation;
3279
+ const acceptedAt =
3280
+ await readPostgresSchedulerRunnerTerminalReceivedAtMs(
3281
+ client,
3282
+ { runId, attempt: runnerAttempt },
3283
+ options,
3284
+ );
3285
+ if (acceptedAt !== null) {
3286
+ const events =
3287
+ await collectGatewayRunnerTerminalProgressLedgerEvents({
3288
+ launch,
3289
+ attempt: runnerAttempt,
3290
+ result: input.runnerResult,
3291
+ events: [],
3292
+ liveLogOffset: 0,
3293
+ suspensionAcceptedAtMs: acceptedAt,
3294
+ });
3295
+ if (events.length > 0) {
3296
+ await appendPostgresRunLedgerEvents(
3297
+ client,
3298
+ { orgId: launch.orgId, runId, events },
3299
+ options,
3300
+ );
3301
+ }
3302
+ finalProgressRetained = true;
3303
+ }
3304
+ // The pushed result proves this exact attempt's runner exited.
3305
+ // Persist its provider-scoped obligation before publishing a wait
3306
+ // that can outlive this worker. Only the cleanup lease deletes it.
3307
+ await enqueuePostgresSchedulerSandboxCleanupForAttempt(
3308
+ client,
3309
+ { runId, attempt: runnerAttempt },
3310
+ options,
3311
+ );
3312
+ }),
3313
+ });
3314
+ }
3315
+ // Local and historical results still require final publication before
3316
+ // advancing their checkpoint. Otherwise all final facts and cleanup are
3317
+ // durable, so progress transport cannot hide the typed waiting state.
3318
+ if (suspension.kind !== 'detached_runner' && !finalProgressRetained) {
3269
3319
  await finalizeSuspensionProgress();
3270
3320
  }
3271
3321
  const outcome = await createPlayRunAttemptSuspensionJob({
@@ -3284,6 +3334,9 @@ async function executeAssignedAbsurdAttempt(input: {
3284
3334
  options,
3285
3335
  transition: 'waiting',
3286
3336
  });
3337
+ if (finalProgressRetained) {
3338
+ await finalizeSuspensionProgress();
3339
+ }
3287
3340
  },
3288
3341
  resumed: async (lifecycle) =>
3289
3342
  await projectLifecycleOutbox({
@@ -3695,9 +3748,6 @@ async function executeAssignedAbsurdAttempt(input: {
3695
3748
  ),
3696
3749
  ),
3697
3750
  inspectDetachedRunner: deps.inspectDetachedRunner,
3698
- deleteRuntimeSandbox: deps.deleteRuntimeSandbox,
3699
- deleteRuntimeSandboxRef: deps.deleteRuntimeSandboxRef,
3700
- sandboxGcTimeoutMs: deps.sandboxGcTimeoutMs,
3701
3751
  }),
3702
3752
  });
3703
3753
  } catch (error) {
@@ -3800,9 +3850,6 @@ async function executeAssignedAbsurdAttempt(input: {
3800
3850
  inspectDetachedRunner: deps.inspectDetachedRunner,
3801
3851
  readDetachedRunnerRuntimeCompletion:
3802
3852
  deps.readDetachedRunnerRuntimeCompletion,
3803
- deleteRuntimeSandbox: deps.deleteRuntimeSandbox,
3804
- deleteRuntimeSandboxRef: deps.deleteRuntimeSandboxRef,
3805
- sandboxGcTimeoutMs: deps.sandboxGcTimeoutMs,
3806
3853
  });
3807
3854
  } else {
3808
3855
  if (backendExecutions > 0) {
@@ -4824,10 +4871,9 @@ export async function enforceRuntimeTrafficPolicyBeforeSandboxStart(input: {
4824
4871
  * terminal transaction;
4825
4872
  * - thrown outcomes -> `releaseAbsurdAttemptRuntimeState` in the caller's
4826
4873
  * catch (B4/FIX 3);
4827
- * - SUSPENDED results -> HERE, immediately: the runner already exited, and
4828
- * the follow-up play park (sleep/HITL) can last hours — waiting for
4829
- * the eventual terminal sweep would leak a dead sandbox for that whole
4830
- * window.
4874
+ * - suspended results -> the existing durable cleanup controller, after
4875
+ * `resolveRunnerSuspension` persists its obligation before typed waiting.
4876
+ * Provider deletion never delays publishing the user's wait.
4831
4877
  */
4832
4878
  async function resolveDetachedRunnerOutcome(input: {
4833
4879
  runId: string;
@@ -4845,9 +4891,6 @@ async function resolveDetachedRunnerOutcome(input: {
4845
4891
  } | null>;
4846
4892
  inspectDetachedRunner?: AbsurdRuntimeWorkerDeps['inspectDetachedRunner'];
4847
4893
  readDetachedRunnerRuntimeCompletion?: AbsurdRuntimeWorkerDeps['readDetachedRunnerRuntimeCompletion'];
4848
- deleteRuntimeSandbox: AbsurdRuntimeWorkerDeps['deleteRuntimeSandbox'];
4849
- deleteRuntimeSandboxRef?: AbsurdRuntimeWorkerDeps['deleteRuntimeSandboxRef'];
4850
- sandboxGcTimeoutMs?: number;
4851
4894
  }): Promise<PlayRunnerResult> {
4852
4895
  const { suspension } = input;
4853
4896
  const runtimeLimitSeconds =
@@ -4873,40 +4916,10 @@ async function resolveDetachedRunnerOutcome(input: {
4873
4916
  }),
4874
4917
  );
4875
4918
  };
4876
- const gcSandboxIfSuspended = async (result: PlayRunnerResult) => {
4877
- if (result.status !== 'suspended') return;
4878
- try {
4879
- const deleteResult = await deleteRuntimeSandboxBounded<
4880
- boolean | RuntimeSandboxDeleteOutcome
4881
- >({
4882
- deleteRuntimeSandbox: input.deleteRuntimeSandboxRef
4883
- ? () =>
4884
- input.deleteRuntimeSandboxRef!(
4885
- runtimeSandboxRefFromSuspension(suspension),
4886
- )
4887
- : input.deleteRuntimeSandbox,
4888
- sandboxId: suspension.sandboxId,
4889
- timeoutMs: input.sandboxGcTimeoutMs ?? ABSURD_SANDBOX_GC_TIMEOUT_MS,
4890
- });
4891
- requireRuntimeSandboxDeleted(
4892
- runtimeSandboxRefFromSuspension(suspension),
4893
- deleteResult,
4894
- );
4895
- } catch (error) {
4896
- console.warn('[absurd-runtime.worker] detached_sandbox_gc_failed', {
4897
- runId: input.runId,
4898
- runnerAttempt: suspension.runnerAttempt,
4899
- sandboxId: suspension.sandboxId,
4900
- stage: 'suspended_result',
4901
- error: error instanceof Error ? error.message : String(error),
4902
- });
4903
- }
4904
- };
4905
4919
  const settle = async (
4906
4920
  result: PlayRunnerResult,
4907
4921
  ): Promise<PlayRunnerResult> => {
4908
4922
  await recordWorkCompletion(result);
4909
- await gcSandboxIfSuspended(result);
4910
4923
  return result;
4911
4924
  };
4912
4925
  const settleFirstWriteWinner = async (
@@ -5548,6 +5561,7 @@ async function salvageAbsurdFinalAttemptTerminal(input: {
5548
5561
  ) {
5549
5562
  try {
5550
5563
  result = await input.deps.runWithClient(async (client) => {
5564
+ let protectWaitingSuspensionFailure = false;
5551
5565
  // A detached-runner wake keeps its attempt `waiting` until setup
5552
5566
  // succeeds. Take that same fence to `running` (the overdue-ceiling
5553
5567
  // claim's transition) so the failure terminal can be written. When
@@ -5555,22 +5569,42 @@ async function salvageAbsurdFinalAttemptTerminal(input: {
5555
5569
  // fence or pushed terminal owns it — the ordinary fenced path below
5556
5570
  // decides.
5557
5571
  if (input.detachedRecovery) {
5558
- await claimPostgresSchedulerWaitingDetachedAttemptForTerminal(
5559
- client,
5560
- {
5561
- runId: input.runId,
5562
- attempt: input.generation,
5563
- leaseToken: input.leaseToken,
5564
- },
5565
- input.options,
5566
- );
5572
+ const detachedClaimed =
5573
+ await claimPostgresSchedulerWaitingDetachedAttemptForTerminal(
5574
+ client,
5575
+ {
5576
+ runId: input.runId,
5577
+ attempt: input.generation,
5578
+ leaseToken: input.leaseToken,
5579
+ },
5580
+ input.options,
5581
+ );
5582
+ if (!detachedClaimed) {
5583
+ const preparation =
5584
+ await claimPostgresSchedulerWaitingSuspensionAttemptForFailure(
5585
+ client,
5586
+ {
5587
+ runId: input.runId,
5588
+ attempt: input.generation,
5589
+ leaseToken: input.leaseToken,
5590
+ },
5591
+ input.options,
5592
+ );
5593
+ if (preparation === 'fence_lost')
5594
+ return { status: 'idle' as const };
5595
+ protectWaitingSuspensionFailure = preparation === 'prepared';
5596
+ }
5567
5597
  }
5568
5598
  return await executePostgresSchedulerClaim(
5569
5599
  client,
5570
5600
  claim,
5571
5601
  async () => failedResult,
5572
5602
  input.detachedRecovery
5573
- ? { ...input.options, preserveRunnerTerminal: true }
5603
+ ? {
5604
+ ...input.options,
5605
+ preserveRunnerTerminal: true,
5606
+ protectWaitingSuspensionFailure,
5607
+ }
5574
5608
  : input.options,
5575
5609
  );
5576
5610
  });
@@ -1086,7 +1086,13 @@ export async function withRuntimeSchedulerClient<T>(
1086
1086
  try {
1087
1087
  const queryResult = await poolClient.query(sql, params);
1088
1088
  throwIfSchedulerAborted(signal);
1089
- return { rows: queryResult.rows };
1089
+ // Simple-protocol transactions return one result per command.
1090
+ // Keep the same rows contract for callers of either protocol.
1091
+ return {
1092
+ rows: Array.isArray(queryResult)
1093
+ ? queryResult.flatMap((result) => result.rows)
1094
+ : queryResult.rows,
1095
+ };
1090
1096
  } catch (error) {
1091
1097
  // node-postgres' query timeout and an AbortSignal reject the
1092
1098
  // promise, but the server may still be processing the SQL.
@@ -39,6 +39,7 @@ import { runtimeWorkCountersFromStats } from '../runtime-work-counters';
39
39
  import {
40
40
  buildPlayRunLedgerEventsFromStatusPatch,
41
41
  createEmptyPlayRunLedgerSnapshot,
42
+ buildTerminalLogReplayEvents,
42
43
  type PlayMapRowActivitySource,
43
44
  type PlayRunLedgerEvent,
44
45
  } from '../run-ledger';
@@ -1902,8 +1903,9 @@ export type DirectWorkerProgressReporter = PostgresSchedulerProgressReporter;
1902
1903
  * network boundary. The returned events are stored in the same Postgres
1903
1904
  * terminal outbox row as `run.completed`, so the projector can append the
1904
1905
  * final step/log/activity facts and the terminal fact in one ordered Convex
1905
- * mutation. This is the completed-run fast path; failed/suspended runs retain
1906
- * the compatibility projection until their parked worker resumes.
1906
+ * mutation. A worker can also retain a suspended runner's complete final
1907
+ * progress before advancing its checkpoint, using the immutable acceptance
1908
+ * timestamp for the same outbox identity across crash or response-loss replay.
1907
1909
  */
1908
1910
  export async function collectGatewayRunnerTerminalProgressLedgerEvents(input: {
1909
1911
  launch: PlaySchedulerSubmitInput;
@@ -1911,14 +1913,17 @@ export async function collectGatewayRunnerTerminalProgressLedgerEvents(input: {
1911
1913
  result: PlayRunnerResult;
1912
1914
  events: unknown[];
1913
1915
  liveLogOffset: number;
1916
+ suspensionAcceptedAtMs?: number;
1914
1917
  }): Promise<PlayRunLedgerEvent[]> {
1915
1918
  const captured: { update?: RuntimeStatusUpdate } = {};
1916
- const reporter = createDirectWorkerProgressReporter({
1919
+ const retainedSuspension = input.suspensionAcceptedAtMs !== undefined;
1920
+ const reporter = createPostgresSchedulerProgressReporter({
1917
1921
  launch: input.launch,
1918
1922
  producerAttempt: input.attempt,
1919
1923
  initialLiveLogTotalCount: input.liveLogOffset,
1920
1924
  isCurrentAttempt: () => true,
1921
1925
  attemptAlreadyVerified: true,
1926
+ durableLogMode: retainedSuspension ? 'progress_reporter' : 'direct_worker',
1922
1927
  publishStatusUpdate: async (update) => {
1923
1928
  captured.update = update;
1924
1929
  },
@@ -1929,6 +1934,7 @@ export async function collectGatewayRunnerTerminalProgressLedgerEvents(input: {
1929
1934
  input.launch.contractSnapshot,
1930
1935
  );
1931
1936
  if (
1937
+ !retainedSuspension &&
1932
1938
  pipeline?.docflow?.nodes.some(
1933
1939
  (node) => node.kind === 'decision' && !node.sketch,
1934
1940
  )
@@ -1977,11 +1983,14 @@ export async function collectGatewayRunnerTerminalProgressLedgerEvents(input: {
1977
1983
  await reporter.finalize(input.result, {
1978
1984
  status: 'running',
1979
1985
  required: true,
1980
- skipTerminalLogReplay: true,
1986
+ skipTerminalLogReplay: !retainedSuspension,
1987
+ replayAllTerminalLogs: retainedSuspension,
1981
1988
  terminalLogProducerAttempt: input.attempt,
1982
1989
  });
1983
1990
  const finalUpdate = captured.update;
1984
1991
  if (!finalUpdate) return [];
1992
+ const checkpointAt =
1993
+ input.suspensionAcceptedAtMs ?? finalUpdate.lastCheckpointAt;
1985
1994
 
1986
1995
  const liveLogs = finalUpdate.liveLogs ?? [];
1987
1996
  const liveLogTotalCount =
@@ -1990,7 +1999,7 @@ export async function collectGatewayRunnerTerminalProgressLedgerEvents(input: {
1990
1999
  patch: {
1991
2000
  runId: input.launch.runId,
1992
2001
  status: 'running',
1993
- lastCheckpointAt: finalUpdate.lastCheckpointAt,
2002
+ lastCheckpointAt: checkpointAt,
1994
2003
  liveNodeProgress: finalUpdate.liveNodeProgress,
1995
2004
  liveLogs,
1996
2005
  liveLogContexts: finalUpdate.liveLogContexts,
@@ -2011,7 +2020,7 @@ export async function collectGatewayRunnerTerminalProgressLedgerEvents(input: {
2011
2020
  type: RUN_EVENT_KIND.workCounters,
2012
2021
  runId: input.launch.runId,
2013
2022
  source: 'worker',
2014
- occurredAt: finalUpdate.lastCheckpointAt ?? Date.now(),
2023
+ occurredAt: checkpointAt ?? Date.now(),
2015
2024
  ...finalUpdate.workCounters,
2016
2025
  });
2017
2026
  }
@@ -2045,5 +2054,19 @@ export async function collectGatewayRunnerTerminalProgressLedgerEvents(input: {
2045
2054
  occurredAt: observation.observedAt,
2046
2055
  observation,
2047
2056
  }));
2048
- return [...progressEvents, ...datasetEvents, ...activityEvents];
2057
+ const replay = finalUpdate.terminalLogReplay;
2058
+ const terminalLogEvents = replay
2059
+ ? buildTerminalLogReplayEvents({
2060
+ runId: input.launch.runId,
2061
+ ...replay,
2062
+ occurredAt: checkpointAt ?? undefined,
2063
+ liveLogTotalCount: replay.totalCount,
2064
+ })
2065
+ : [];
2066
+ return [
2067
+ ...progressEvents,
2068
+ ...datasetEvents,
2069
+ ...activityEvents,
2070
+ ...terminalLogEvents,
2071
+ ];
2049
2072
  }