deepline 0.3.236 → 0.3.237

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.
@@ -2047,6 +2047,8 @@ export type BillingRecentUsageEntry = {
2047
2047
  | 'pending'
2048
2048
  | 'free'
2049
2049
  | 'failed';
2050
+ /** Final billing truth may be unknown even after execution completes. */
2051
+ charge_finality?: 'final' | 'pending' | 'unknown';
2050
2052
  rough_usd_cost: number | null;
2051
2053
  created_at: string;
2052
2054
  };
@@ -2076,6 +2078,10 @@ export type BillingUsageResult = {
2076
2078
  run_id: string | null;
2077
2079
  request_id?: string;
2078
2080
  total: number;
2081
+ /** Only true when total covers the complete matching history. */
2082
+ total_is_exact?: boolean;
2083
+ /** Proven matching entries, without assuming unscanned rows match. */
2084
+ total_lower_bound?: number;
2079
2085
  offset: number;
2080
2086
  limit: number;
2081
2087
  next_offset: number;
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.236',
203
+ version: '0.3.237',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -136,6 +136,8 @@ import {
136
136
  completePostgresSchedulerComputeBillingSessionBootstrap,
137
137
  deferPostgresSchedulerAttemptForCapacity,
138
138
  enqueuePostgresSchedulerSandboxCleanupForAttempt,
139
+ enqueuePostgresSchedulerSandboxCleanupForRun,
140
+ hasPostgresSchedulerRecoverableDetachedRunner,
139
141
  executePostgresSchedulerClaim,
140
142
  ensurePostgresSchedulerTerminalComputeBillingJob,
141
143
  failPostgresSchedulerAttempt,
@@ -760,7 +762,7 @@ export type AbsurdRuntimeWorkerDeps = {
760
762
  deleteRuntimeSandboxRef?: (
761
763
  ref: RuntimeSandboxRef,
762
764
  ) => Promise<RuntimeSandboxDeleteOutcome>;
763
- deleteRuntimeSandboxWithOutcome: (
765
+ deleteRuntimeSandboxWithOutcome?: (
764
766
  sandboxId: string,
765
767
  expectedOrganizationId: string | null,
766
768
  ) => Promise<DaytonaSandboxDeleteOutcome>;
@@ -885,40 +887,48 @@ function buildDefaultDeps(absurd: Absurd): AbsurdRuntimeWorkerDeps {
885
887
  * of silently leaving the CLI stale.
886
888
  */
887
889
  async function projectLifecycleOutbox(input: {
890
+ ctx: TaskContext;
888
891
  deps: AbsurdRuntimeWorkerDeps;
889
892
  launch: PlaySchedulerSubmitInput;
890
893
  lifecycle: PostgresSchedulerLifecycleOutboxEvent;
891
894
  options?: Pick<PostgresSchedulerOptions, 'schema'>;
892
895
  transition: 'waiting' | 'resumed';
893
896
  }): Promise<void> {
894
- try {
895
- const result = await input.deps.projectOutbox(
896
- input.launch,
897
- input.lifecycle.runId,
898
- input.options,
899
- input.lifecycle.eventId,
900
- );
901
- if (result && !result.targetDelivered) {
902
- console.error('[run-ledger-outbox.lifecycle_projection_pending]', {
903
- runId: input.lifecycle.runId,
904
- eventId: input.lifecycle.eventId,
905
- transition: input.transition,
906
- projected: result.projected,
907
- failed: result.failed,
908
- failedEventIds: result.failedEventIds,
909
- });
910
- }
911
- } catch (error) {
912
- // Scheduler truth and the outbox row were committed atomically before this
913
- // handoff. Keep the durable continuation live so recovery can heal a
914
- // transient app-runtime failure, but do not hide the visibility gap.
915
- console.error('[run-ledger-outbox.lifecycle_projection_failed]', {
916
- runId: input.lifecycle.runId,
917
- eventId: input.lifecycle.eventId,
918
- transition: input.transition,
919
- error: error instanceof Error ? error.message : String(error),
920
- });
921
- }
897
+ await runWithAbsurdExecutionLeaseHeartbeat({
898
+ ctx: input.ctx,
899
+ runId: input.lifecycle.runId,
900
+ operation: async (signal) => {
901
+ try {
902
+ const result = await input.deps.projectOutbox(
903
+ input.launch,
904
+ input.lifecycle.runId,
905
+ input.options,
906
+ input.lifecycle.eventId,
907
+ signal,
908
+ );
909
+ if (result && !result.targetDelivered) {
910
+ console.error('[run-ledger-outbox.lifecycle_projection_pending]', {
911
+ runId: input.lifecycle.runId,
912
+ eventId: input.lifecycle.eventId,
913
+ transition: input.transition,
914
+ projected: result.projected,
915
+ failed: result.failed,
916
+ failedEventIds: result.failedEventIds,
917
+ });
918
+ }
919
+ } catch (error) {
920
+ // Scheduler truth and the outbox row were committed atomically before this
921
+ // handoff. Keep the durable continuation live so recovery can heal a
922
+ // transient app-runtime failure, but do not hide the visibility gap.
923
+ console.error('[run-ledger-outbox.lifecycle_projection_failed]', {
924
+ runId: input.lifecycle.runId,
925
+ eventId: input.lifecycle.eventId,
926
+ transition: input.transition,
927
+ error: error instanceof Error ? error.message : String(error),
928
+ });
929
+ }
930
+ },
931
+ });
922
932
  }
923
933
 
924
934
  function nonJsonResponseTitle(
@@ -2422,23 +2432,26 @@ async function executeAbsurdAttemptAdapter(
2422
2432
  let executionLaunch = launch;
2423
2433
  try {
2424
2434
  const tokenStartedAt = performance.now();
2425
- const executorToken = await deps
2426
- .mintAttemptExecutorToken({
2427
- launch,
2428
- attempt: generation,
2429
- leaseToken,
2430
- schedulerSchema: options?.schema ?? null,
2431
- })
2432
- .finally(() => {
2433
- console.info(
2434
- '[absurd-runtime.worker.phase] ' +
2435
- JSON.stringify({
2436
- runId,
2437
- phase: 'executor_token_mint',
2438
- totalMs: performance.now() - tokenStartedAt,
2439
- }),
2440
- );
2441
- });
2435
+ const executorToken = await runWithAbsurdExecutionLeaseHeartbeat({
2436
+ ctx,
2437
+ runId,
2438
+ operation: () =>
2439
+ deps.mintAttemptExecutorToken({
2440
+ launch,
2441
+ attempt: generation,
2442
+ leaseToken,
2443
+ schedulerSchema: options?.schema ?? null,
2444
+ }),
2445
+ }).finally(() => {
2446
+ console.info(
2447
+ '[absurd-runtime.worker.phase] ' +
2448
+ JSON.stringify({
2449
+ runId,
2450
+ phase: 'executor_token_mint',
2451
+ totalMs: performance.now() - tokenStartedAt,
2452
+ }),
2453
+ );
2454
+ });
2442
2455
  executionLaunch = {
2443
2456
  ...launch,
2444
2457
  executorToken,
@@ -2459,12 +2472,56 @@ async function executeAbsurdAttemptAdapter(
2459
2472
  runtimeApi,
2460
2473
  });
2461
2474
  } catch (error) {
2462
- if (
2463
- error instanceof SuspendTask ||
2464
- error instanceof CancelledTask ||
2465
- error instanceof FailedTask ||
2466
- error instanceof DeferTask
2467
- ) {
2475
+ if (error instanceof SuspendTask || error instanceof DeferTask) throw error;
2476
+ let detached: Awaited<
2477
+ ReturnType<typeof resumePostgresSchedulerDetachedRunnerAttempt>
2478
+ >;
2479
+ try {
2480
+ detached = await deps.runWithClient((client) =>
2481
+ resumePostgresSchedulerDetachedRunnerAttempt(
2482
+ client,
2483
+ { runId, attempt: generation, leaseToken },
2484
+ options,
2485
+ ),
2486
+ );
2487
+ } catch (recoveryError) {
2488
+ console.error(
2489
+ '[absurd-runtime.worker] detached_runner_recovery_unavailable',
2490
+ JSON.stringify({
2491
+ runId,
2492
+ attempt: generation,
2493
+ engineTaskId: ctx.taskID,
2494
+ error: error instanceof Error ? error.message : String(error),
2495
+ recoveryError:
2496
+ recoveryError instanceof Error
2497
+ ? recoveryError.message
2498
+ : String(recoveryError),
2499
+ cleanupAuthorized: false,
2500
+ }),
2501
+ );
2502
+ throw new AggregateError(
2503
+ [error, recoveryError],
2504
+ 'Scheduler could not prove detached runner cleanup authority.',
2505
+ );
2506
+ }
2507
+ if (detached) {
2508
+ console.error(
2509
+ '[absurd-runtime.worker] detached_runner_handoff_recovered',
2510
+ JSON.stringify({
2511
+ runId,
2512
+ attempt: generation,
2513
+ engineAttempt: ctx.attempt,
2514
+ engineTaskId: ctx.taskID,
2515
+ error: error instanceof Error ? error.message : String(error),
2516
+ cleanupAuthorized: false,
2517
+ }),
2518
+ );
2519
+ throw new DeferTask(
2520
+ `Detached runner ${runId}/${generation} retains execution after scheduler handoff failure.`,
2521
+ 1,
2522
+ );
2523
+ }
2524
+ if (error instanceof CancelledTask || error instanceof FailedTask) {
2468
2525
  // Park/cancel signals belong to the engine; never treat them as leg
2469
2526
  // failures.
2470
2527
  throw error;
@@ -2786,11 +2843,6 @@ async function executeAssignedAbsurdAttempt(input: {
2786
2843
  `absurd runtime worker: suspended runner result for ${runId} carried no suspension.`,
2787
2844
  );
2788
2845
  }
2789
- await input.progressReporter.finalize(input.runnerResult, {
2790
- status: 'running',
2791
- replayAllTerminalLogs: input.fromDetachedRunner,
2792
- terminalLogProducerAttempt: input.terminalLogProducerAttempt,
2793
- });
2794
2846
  const suspendedCheckpoint = absurdCheckpointBase(
2795
2847
  input.runnerResult.checkpoint ?? checkpoint ?? null,
2796
2848
  );
@@ -2837,16 +2889,29 @@ async function executeAssignedAbsurdAttempt(input: {
2837
2889
  store: attemptStore,
2838
2890
  engine,
2839
2891
  lifecycle: {
2840
- waiting: async (lifecycle) =>
2892
+ waiting: async (lifecycle) => {
2893
+ await runWithAbsurdExecutionLeaseHeartbeat({
2894
+ ctx,
2895
+ runId,
2896
+ operation: () =>
2897
+ input.progressReporter.finalize(input.runnerResult, {
2898
+ status: 'running',
2899
+ replayAllTerminalLogs: input.fromDetachedRunner,
2900
+ terminalLogProducerAttempt: input.terminalLogProducerAttempt,
2901
+ }),
2902
+ });
2841
2903
  await projectLifecycleOutbox({
2904
+ ctx,
2842
2905
  deps,
2843
2906
  launch,
2844
2907
  lifecycle,
2845
2908
  options,
2846
2909
  transition: 'waiting',
2847
- }),
2910
+ });
2911
+ },
2848
2912
  resumed: async (lifecycle) =>
2849
2913
  await projectLifecycleOutbox({
2914
+ ctx,
2850
2915
  deps,
2851
2916
  launch,
2852
2917
  lifecycle,
@@ -2941,6 +3006,7 @@ async function executeAssignedAbsurdAttempt(input: {
2941
3006
  return { ok: true, runId, schedulerStatus: 'idle' };
2942
3007
  }
2943
3008
  await projectLifecycleOutbox({
3009
+ ctx,
2944
3010
  deps,
2945
3011
  launch,
2946
3012
  lifecycle: marked,
@@ -2987,6 +3053,7 @@ async function executeAssignedAbsurdAttempt(input: {
2987
3053
  return { ok: true, runId, schedulerStatus: 'idle' };
2988
3054
  }
2989
3055
  await projectLifecycleOutbox({
3056
+ ctx,
2990
3057
  deps,
2991
3058
  launch,
2992
3059
  lifecycle: running,
@@ -3068,6 +3135,16 @@ async function executeAssignedAbsurdAttempt(input: {
3068
3135
  error: unknown,
3069
3136
  runnerConfig: PlayRunnerExecutionConfig,
3070
3137
  ): Promise<void> => {
3138
+ if (
3139
+ await deps.runWithClient((client) =>
3140
+ hasPostgresSchedulerRecoverableDetachedRunner(
3141
+ client,
3142
+ { runId, attempt: generation, leaseToken },
3143
+ options,
3144
+ ),
3145
+ )
3146
+ )
3147
+ return;
3071
3148
  const cleanupTerminalReason =
3072
3149
  error instanceof CancelledTask ? 'lease_expired' : 'runner_failed';
3073
3150
  await deps
@@ -3127,33 +3204,52 @@ async function executeAssignedAbsurdAttempt(input: {
3127
3204
  ),
3128
3205
  )
3129
3206
  .catch(() => attemptResources);
3130
- // Deleting without a durable obligation turns a transient provider failure
3131
- // into an untracked occupied sandbox. Preserve the recorded resource for
3132
- // terminal/retry recovery when the enqueue cannot commit; receipt and
3133
- // sheet release below remain safe and attempt-scoped.
3134
- const immediateSandboxGcAllowed =
3135
- !attemptResourceReadFailed &&
3136
- (attemptResources.length === 0 ||
3137
- (await persistAttemptSandboxCleanupBeforeBestEffortGc({
3138
- deps,
3139
- runId,
3140
- attempt: generation,
3207
+ // All provider deletion goes through the durable cleanup lease, including
3208
+ // retired generations. Never pass a read-only resource snapshot to raw GC.
3209
+ if (!attemptResourceReadFailed) {
3210
+ await deps.runWithClient((client) =>
3211
+ enqueuePostgresSchedulerSandboxCleanupForRun(
3212
+ client,
3213
+ { runId },
3141
3214
  options,
3142
- })));
3215
+ ),
3216
+ );
3217
+ const environments = new Set(
3218
+ allResources.flatMap((resource) => {
3219
+ if (!runtimeSandboxRefFromResource(resource)) return [];
3220
+ const value = resource as {
3221
+ runtimeEnvironment?: string;
3222
+ daytonaEnvironment?: string;
3223
+ };
3224
+ return [
3225
+ value.runtimeEnvironment === 'preview' ||
3226
+ value.daytonaEnvironment === 'preview'
3227
+ ? ('preview' as const)
3228
+ : ('production' as const),
3229
+ ];
3230
+ }),
3231
+ );
3232
+ for (const environment of environments) {
3233
+ await drainPostgresSchedulerSandboxCleanupOnce({
3234
+ runWithClient: deps.runWithClient,
3235
+ deleteRuntimeSandbox: deps.deleteRuntimeSandbox,
3236
+ deleteRuntimeSandboxRef: deps.deleteRuntimeSandboxRef,
3237
+ deleteRuntimeSandboxWithOutcome: deps.deleteRuntimeSandboxWithOutcome,
3238
+ readSandboxCrashDiagnostic: deps.readSandboxCrashDiagnostic,
3239
+ leaseOwner: `attempt-error:${runId}:${generation}`,
3240
+ runId,
3241
+ environment,
3242
+ schedulerOptions: options,
3243
+ timeoutMs: deps.sandboxGcTimeoutMs,
3244
+ });
3245
+ }
3246
+ }
3143
3247
  const runtimeStateCleanup = releaseAbsurdAttemptRuntimeState({
3144
3248
  config: runnerConfig,
3145
3249
  runId,
3146
3250
  runAttempt: generation,
3147
3251
  resources: attemptResources,
3148
- sandboxRefs: runtimeSandboxResourceRefs(allResources),
3149
3252
  reason: cleanupTerminalReason,
3150
- deleteRuntimeSandbox: immediateSandboxGcAllowed
3151
- ? deps.deleteRuntimeSandbox
3152
- : undefined,
3153
- deleteRuntimeSandboxRef: immediateSandboxGcAllowed
3154
- ? deps.deleteRuntimeSandboxRef
3155
- : undefined,
3156
- sandboxGcTimeoutMs: deps.sandboxGcTimeoutMs,
3157
3253
  });
3158
3254
  if (ctx.maxAttempts !== null && ctx.attempt >= ctx.maxAttempts) {
3159
3255
  // A final engine attempt cannot retry. Its durable failed terminal must
@@ -3190,30 +3286,41 @@ async function executeAssignedAbsurdAttempt(input: {
3190
3286
  pendingDetachedRunner = null;
3191
3287
  let runnerResult: PlayRunnerResult;
3192
3288
  try {
3193
- runnerResult = await resolveDetachedRunnerOutcome({
3289
+ runnerResult = await runWithAbsurdExecutionLeaseHeartbeat({
3290
+ ctx,
3194
3291
  runId,
3195
- suspension: detached,
3196
- checkpoint: absurdCheckpointBase(checkpoint),
3197
- readRunnerTerminal,
3198
- recordRunnerTerminal,
3199
- readRunnerLiveness: () =>
3200
- deps.runWithClient((client) =>
3201
- readPostgresSchedulerRunnerLiveness(
3202
- client,
3203
- { runId, attempt: detached.runnerAttempt },
3204
- options,
3205
- ),
3206
- ),
3207
- inspectDetachedRunner: deps.inspectDetachedRunner,
3208
- deleteRuntimeSandbox: deps.deleteRuntimeSandbox,
3209
- deleteRuntimeSandboxRef: deps.deleteRuntimeSandboxRef,
3210
- sandboxGcTimeoutMs: deps.sandboxGcTimeoutMs,
3292
+ operation: () =>
3293
+ resolveDetachedRunnerOutcome({
3294
+ runId,
3295
+ suspension: detached,
3296
+ checkpoint: absurdCheckpointBase(checkpoint),
3297
+ readRunnerTerminal,
3298
+ recordRunnerTerminal,
3299
+ readRunnerLiveness: () =>
3300
+ deps.runWithClient((client) =>
3301
+ readPostgresSchedulerRunnerLiveness(
3302
+ client,
3303
+ { runId, attempt: detached.runnerAttempt },
3304
+ options,
3305
+ ),
3306
+ ),
3307
+ inspectDetachedRunner: deps.inspectDetachedRunner,
3308
+ deleteRuntimeSandbox: deps.deleteRuntimeSandbox,
3309
+ deleteRuntimeSandboxRef: deps.deleteRuntimeSandboxRef,
3310
+ sandboxGcTimeoutMs: deps.sandboxGcTimeoutMs,
3311
+ }),
3211
3312
  });
3212
3313
  } catch (error) {
3213
3314
  // The terminal fast path skips normal runner setup. Build its cleanup
3214
3315
  // context only on failure, then preserve the same pre-retry resource
3215
3316
  // release and sandbox-GC fence as the full execution path.
3216
- const preparedRunnerConfig = await prepareRunnerConfig(claim);
3317
+ const preparedRunnerConfig = await runWithAbsurdExecutionLeaseHeartbeat(
3318
+ {
3319
+ ctx,
3320
+ runId,
3321
+ operation: () => prepareRunnerConfig(claim),
3322
+ },
3323
+ );
3217
3324
  try {
3218
3325
  await cleanupAfterExecutionError(
3219
3326
  error,
@@ -3261,7 +3368,11 @@ async function executeAssignedAbsurdAttempt(input: {
3261
3368
  }
3262
3369
 
3263
3370
  const progressReporter = deps.createProgressReporter(launch, generation);
3264
- const preparedRunnerConfig = await prepareRunnerConfig(claim);
3371
+ const preparedRunnerConfig = await runWithAbsurdExecutionLeaseHeartbeat({
3372
+ ctx,
3373
+ runId,
3374
+ operation: () => prepareRunnerConfig(claim),
3375
+ });
3265
3376
  const runnerConfig = preparedRunnerConfig.runnerConfig;
3266
3377
 
3267
3378
  let runnerResult: PlayRunnerResult;
@@ -5028,6 +5139,9 @@ export function createAbsurdRuntimeTasks(
5028
5139
  if (deps?.deleteRuntimeSandbox && !deps.deleteRuntimeSandboxRef) {
5029
5140
  resolved.deleteRuntimeSandboxRef = undefined;
5030
5141
  }
5142
+ if (deps?.deleteRuntimeSandbox && !deps.deleteRuntimeSandboxWithOutcome) {
5143
+ resolved.deleteRuntimeSandboxWithOutcome = undefined;
5144
+ }
5031
5145
  absurd.registerTask({ name: ABSURD_PLAY_RUN_TASK, queue }, (params, ctx) =>
5032
5146
  executeAbsurdRuntimeLaunch(
5033
5147
  params as AbsurdPlayRunTaskParams,
@@ -5145,11 +5259,15 @@ async function readOpenAbsurdEngineRunId(input: {
5145
5259
  * can win that same terminalization race while a replacement is starting. In
5146
5260
  * that case Absurd rejects `complete_run` because the engine is already
5147
5261
  * terminal; that is an idempotent success, not a reason to flap the new worker.
5262
+ * A receipt can also arrive before its wake is claimed. Retire that pending or
5263
+ * sleeping task with the existing cancellation primitive after scheduler truth
5264
+ * is durable, rather than failing recovery because complete_run needs a claim.
5148
5265
  */
5149
5266
  async function completeAbsurdEngineRunIfStillRunning(input: {
5150
5267
  client: PostgresSchedulerQueryClient;
5151
5268
  queue: string;
5152
5269
  absurdRunId: string;
5270
+ absurdTaskId: string;
5153
5271
  result: unknown;
5154
5272
  }): Promise<boolean> {
5155
5273
  try {
@@ -5178,6 +5296,13 @@ async function completeAbsurdEngineRunIfStillRunning(input: {
5178
5296
  ) {
5179
5297
  return false;
5180
5298
  }
5299
+ if (state === 'pending' || state === 'sleeping') {
5300
+ return await cancelAbsurdEngineTaskAfterSchedulerTerminal({
5301
+ client: input.client,
5302
+ queue: input.queue,
5303
+ absurdTaskId: input.absurdTaskId,
5304
+ });
5305
+ }
5181
5306
  throw error;
5182
5307
  }
5183
5308
  }
@@ -5231,9 +5356,9 @@ async function cancelAbsurdEngineTaskAfterSchedulerTerminal(input: {
5231
5356
  * its eventual lease reclaim would allocate a fresh scheduler generation and
5232
5357
  * re-run a play that has already completed. This recovery uses the original
5233
5358
  * attempt lease and the exact same terminal path as the normal wake leg, then
5234
- * marks the engine run complete. It is deliberately limited to `completed`
5235
- * results: failure and suspension retain the engine's normal retry/park
5236
- * semantics once the scaler brings a recovery worker back for the lane.
5359
+ * closes an engine run when it still exists. Completed and failed receipts
5360
+ * remain authoritative even after the final engine claim expires. Suspended
5361
+ * receipts retain the normal continuation handoff.
5237
5362
  */
5238
5363
  export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
5239
5364
  client: PostgresSchedulerQueryClient;
@@ -5253,6 +5378,7 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
5253
5378
  {
5254
5379
  limit: input.limit,
5255
5380
  absurdReleaseId: input.absurdReleaseId,
5381
+ includeFailed: true,
5256
5382
  },
5257
5383
  options,
5258
5384
  );
@@ -5261,7 +5387,10 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
5261
5387
 
5262
5388
  for (const terminal of terminals) {
5263
5389
  const runnerResult = parseDetachedRunnerResult(terminal.result);
5264
- if (runnerResult?.status !== 'completed') {
5390
+ if (
5391
+ runnerResult?.status !== 'completed' &&
5392
+ runnerResult?.status !== 'failed'
5393
+ ) {
5265
5394
  skipped += 1;
5266
5395
  continue;
5267
5396
  }
@@ -5270,10 +5399,6 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
5270
5399
  queue: input.queue,
5271
5400
  schedulerRunId: terminal.runId,
5272
5401
  });
5273
- if (!absurdRun) {
5274
- skipped += 1;
5275
- continue;
5276
- }
5277
5402
  const launch = await readPostgresSchedulerLaunch(
5278
5403
  input.client,
5279
5404
  terminal.runId,
@@ -5292,6 +5417,16 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
5292
5417
  let schedulerStatus: 'completed' | 'failed';
5293
5418
  let terminalizedByAnotherWorker = false;
5294
5419
  try {
5420
+ await markPostgresSchedulerAttemptRunning(
5421
+ input.client,
5422
+ {
5423
+ runId: terminal.runId,
5424
+ attempt: terminal.attempt,
5425
+ leaseToken: terminal.leaseToken,
5426
+ preserveHeartbeatAt: true,
5427
+ },
5428
+ options,
5429
+ );
5295
5430
  const settled = await executePostgresSchedulerClaim(
5296
5431
  input.client,
5297
5432
  {
@@ -5321,22 +5456,26 @@ export async function recoverAbsurdRuntimeCompletedRunnerTerminals(input: {
5321
5456
  terminal.runId,
5322
5457
  options,
5323
5458
  );
5324
- if (observed?.status !== 'completed') throw error;
5325
- schedulerStatus = 'completed';
5459
+ if (observed?.status !== 'completed' && observed?.status !== 'failed')
5460
+ throw error;
5461
+ schedulerStatus = observed.status;
5326
5462
  terminalizedByAnotherWorker = true;
5327
5463
  }
5328
5464
 
5329
- const engineCompleted = await completeAbsurdEngineRunIfStillRunning({
5330
- client: input.client,
5331
- queue: input.queue,
5332
- absurdRunId: absurdRun.runId,
5333
- result: {
5334
- recovered: terminalizedByAnotherWorker
5335
- ? 'completed_runner_terminal_race'
5336
- : 'completed_runner_terminal',
5337
- schedulerStatus,
5338
- },
5339
- });
5465
+ const engineCompleted = absurdRun
5466
+ ? await completeAbsurdEngineRunIfStillRunning({
5467
+ client: input.client,
5468
+ queue: input.queue,
5469
+ absurdRunId: absurdRun.runId,
5470
+ absurdTaskId: absurdRun.taskId,
5471
+ result: {
5472
+ recovered: terminalizedByAnotherWorker
5473
+ ? 'completed_runner_terminal_race'
5474
+ : 'completed_runner_terminal',
5475
+ schedulerStatus,
5476
+ },
5477
+ })
5478
+ : false;
5340
5479
  if (terminalizedByAnotherWorker) {
5341
5480
  if (engineCompleted) recovered += 1;
5342
5481
  else skipped += 1;
@@ -6358,6 +6497,7 @@ function unavailableSandboxCrashDiagnostic(
6358
6497
  async function claimPostgresSchedulerSandboxCleanupBatch(input: {
6359
6498
  runWithClient: AbsurdRuntimeWorkerDeps['runWithClient'];
6360
6499
  leaseOwner: string;
6500
+ runId?: string;
6361
6501
  environment: 'preview' | 'production';
6362
6502
  schedulerOptions: Pick<PostgresSchedulerOptions, 'schema'>;
6363
6503
  limit: number;
@@ -6368,6 +6508,7 @@ async function claimPostgresSchedulerSandboxCleanupBatch(input: {
6368
6508
  client,
6369
6509
  {
6370
6510
  leaseOwner: input.leaseOwner,
6511
+ runId: input.runId,
6371
6512
  environment: input.environment,
6372
6513
  limit: input.limit,
6373
6514
  providers: input.providers,
@@ -6655,7 +6796,8 @@ async function processPostgresSchedulerSandboxCleanupJob(input: {
6655
6796
  provider: job.provider,
6656
6797
  sandboxId: job.sandboxId,
6657
6798
  cleanupAttempt: job.attemptCount,
6658
- terminalStatus: job.runStatusAtClaim,
6799
+ runStatusAtClaim: job.runStatusAtClaim,
6800
+ cleanupAuthority: 'durable_cleanup_claim',
6659
6801
  providerOutcome: outcome.providerOutcome,
6660
6802
  completionOutcome: outcome.completion,
6661
6803
  });
@@ -6681,6 +6823,7 @@ export async function drainPostgresSchedulerSandboxCleanupOnce(input: {
6681
6823
  deleteRuntimeSandboxRef?: AbsurdRuntimeWorkerDeps['deleteRuntimeSandboxRef'];
6682
6824
  deleteRuntimeSandboxWithOutcome?: AbsurdRuntimeWorkerDeps['deleteRuntimeSandboxWithOutcome'];
6683
6825
  leaseOwner: string;
6826
+ runId?: string;
6684
6827
  environment: 'preview' | 'production';
6685
6828
  schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
6686
6829
  limit?: number;
@@ -6702,6 +6845,7 @@ export async function drainPostgresSchedulerSandboxCleanupOnce(input: {
6702
6845
  const jobs = await claimPostgresSchedulerSandboxCleanupBatch({
6703
6846
  runWithClient: input.runWithClient,
6704
6847
  leaseOwner: input.leaseOwner,
6848
+ runId: input.runId,
6705
6849
  environment: input.environment,
6706
6850
  schedulerOptions: options,
6707
6851
  limit,