deepline 0.3.235 → 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.
@@ -6858,7 +6858,7 @@ export async function assignPostgresSchedulerRunAttempt(
6858
6858
  */
6859
6859
  export async function resumePostgresSchedulerDetachedRunnerAttempt(
6860
6860
  client: PostgresSchedulerQueryClient,
6861
- input: { runId: string },
6861
+ input: { runId: string; attempt?: number; leaseToken?: string },
6862
6862
  options?: Pick<PostgresSchedulerOptions, 'schema'>,
6863
6863
  ): Promise<{
6864
6864
  attempt: number;
@@ -6873,27 +6873,77 @@ export async function resumePostgresSchedulerDetachedRunnerAttempt(
6873
6873
  suspension_json: unknown;
6874
6874
  }>(
6875
6875
  `
6876
- WITH waiting_run AS (
6877
- SELECT run_id, checkpoint_json, suspension_json
6876
+ WITH detached_run AS MATERIALIZED (
6877
+ SELECT run_id, status, checkpoint_json, suspension_json
6878
6878
  FROM ${tableName('runs', options)}
6879
6879
  WHERE run_id = $1
6880
- AND status = 'waiting'
6880
+ AND status IN ('running', 'waiting')
6881
6881
  AND suspension_json ->> 'kind' = 'detached_runner'
6882
+ AND NOT EXISTS (
6883
+ SELECT 1 FROM ${tableName('control', options)} AS control
6884
+ WHERE control.key = 'runtime_cancel:' || $1
6885
+ )
6882
6886
  FOR UPDATE
6887
+ ), detached_attempt AS MATERIALIZED (
6888
+ SELECT attempt.run_id, attempt.attempt, attempt.status, attempt.lease_token
6889
+ FROM ${tableName('attempts', options)} AS attempt
6890
+ JOIN detached_run ON attempt.run_id = detached_run.run_id
6891
+ WHERE attempt.attempt = (detached_run.suspension_json ->> 'runnerAttempt')::integer
6892
+ AND attempt.status IN ('running', 'waiting')
6893
+ AND attempt.lease_token IS NOT NULL
6894
+ AND ($2::integer IS NULL OR attempt.attempt = $2)
6895
+ AND ($3::text IS NULL OR attempt.lease_token = $3)
6896
+ AND NOT EXISTS (
6897
+ SELECT 1 FROM ${tableName('attempts', options)} AS newer
6898
+ WHERE newer.run_id = attempt.run_id AND newer.attempt > attempt.attempt
6899
+ AND newer.status IN ('claimed', 'running', 'waiting', 'completed', 'failed')
6900
+ )
6901
+ FOR UPDATE OF attempt
6902
+ ), restored_attempt AS (
6903
+ UPDATE ${tableName('attempts', options)} AS target
6904
+ SET status = 'waiting', updated_at = now()
6905
+ FROM detached_attempt
6906
+ WHERE target.run_id = detached_attempt.run_id
6907
+ AND target.attempt = detached_attempt.attempt
6908
+ AND target.lease_token = detached_attempt.lease_token
6909
+ AND target.status = 'running'
6910
+ RETURNING target.run_id
6911
+ ), restored_run AS (
6912
+ UPDATE ${tableName('runs', options)} AS target
6913
+ SET status = 'waiting',
6914
+ wait_deadline = CASE
6915
+ WHEN jsonb_typeof(target.suspension_json -> 'startedAtMs') = 'number'
6916
+ AND jsonb_typeof(target.suspension_json -> 'ceilingMs') = 'number'
6917
+ THEN to_timestamp(
6918
+ ((target.suspension_json ->> 'startedAtMs')::double precision +
6919
+ (target.suspension_json ->> 'ceilingMs')::double precision) / 1000
6920
+ ) ELSE target.wait_deadline END,
6921
+ updated_at = now()
6922
+ WHERE target.run_id IN (SELECT run_id FROM detached_attempt)
6923
+ AND target.status = 'running'
6924
+ RETURNING target.run_id
6925
+ ), restored_outbox AS (
6926
+ INSERT INTO ${tableName('outbox', options)} (
6927
+ event_id, run_id, event_type, payload_json, created_at
6928
+ )
6929
+ SELECT $4, run_id, 'run.waiting', jsonb_build_object('kind', 'detached_runner'),
6930
+ ${causalOutboxCreatedAt('restored_run.run_id', options)}
6931
+ FROM restored_run
6932
+ RETURNING run_id
6883
6933
  )
6884
- SELECT attempts.attempt,
6885
- attempts.lease_token,
6886
- waiting_run.checkpoint_json,
6887
- waiting_run.suspension_json
6888
- FROM ${tableName('attempts', options)} AS attempts
6889
- JOIN waiting_run ON attempts.run_id = waiting_run.run_id
6890
- WHERE attempts.attempt = (waiting_run.suspension_json ->> 'runnerAttempt')::integer
6891
- AND attempts.status = 'waiting'
6934
+ SELECT detached_attempt.attempt, detached_attempt.lease_token,
6935
+ detached_run.checkpoint_json, detached_run.suspension_json
6936
+ FROM detached_attempt JOIN detached_run USING (run_id)
6892
6937
  `,
6893
- [input.runId],
6938
+ [
6939
+ input.runId,
6940
+ input.attempt ?? null,
6941
+ input.leaseToken ?? null,
6942
+ `run-waiting:${input.runId}:${randomUUID()}`,
6943
+ ],
6894
6944
  );
6895
6945
  const row = resumed.rows[0];
6896
- if (!row || !row.lease_token) return null;
6946
+ if (!row) return null;
6897
6947
  return {
6898
6948
  attempt: Number(row.attempt),
6899
6949
  leaseToken: row.lease_token,
@@ -6902,6 +6952,33 @@ export async function resumePostgresSchedulerDetachedRunnerAttempt(
6902
6952
  };
6903
6953
  }
6904
6954
 
6955
+ /** A worker error cannot settle/release an execution owned by the detached
6956
+ * runner, including one whose authoritative result is waiting to be reduced. */
6957
+ export async function hasPostgresSchedulerRecoverableDetachedRunner(
6958
+ client: PostgresSchedulerQueryClient,
6959
+ input: { runId: string; attempt: number; leaseToken: string },
6960
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
6961
+ ): Promise<boolean> {
6962
+ const result = await client.query<{ recoverable: boolean }>(
6963
+ `SELECT EXISTS (
6964
+ SELECT 1 FROM ${tableName('runs', options)} AS run
6965
+ JOIN ${tableName('attempts', options)} AS attempt ON attempt.run_id = run.run_id
6966
+ WHERE run.run_id = $1 AND attempt.attempt = $2 AND attempt.lease_token = $3
6967
+ AND run.status IN ('running', 'waiting')
6968
+ AND run.suspension_json ->> 'kind' = 'detached_runner'
6969
+ AND run.suspension_json -> 'runnerAttempt' = to_jsonb(attempt.attempt)
6970
+ AND attempt.status IN ('running', 'waiting')
6971
+ AND NOT EXISTS (SELECT 1 FROM ${tableName('control', options)} AS control
6972
+ WHERE control.key = 'runtime_cancel:' || run.run_id)
6973
+ AND NOT EXISTS (SELECT 1 FROM ${tableName('attempts', options)} AS newer
6974
+ WHERE newer.run_id = run.run_id AND newer.attempt > attempt.attempt
6975
+ AND newer.status IN ('claimed', 'running', 'waiting', 'completed', 'failed'))
6976
+ ) AS recoverable`,
6977
+ [input.runId, input.attempt, input.leaseToken],
6978
+ );
6979
+ return result.rows[0]?.recoverable === true;
6980
+ }
6981
+
6905
6982
  export async function markPostgresSchedulerAttemptWaiting(
6906
6983
  client: PostgresSchedulerQueryClient,
6907
6984
  input: {
@@ -7087,7 +7164,9 @@ export async function markPostgresSchedulerAttemptRunning(
7087
7164
  running_run AS (
7088
7165
  UPDATE ${tableName('runs', options)} AS target
7089
7166
  SET status = 'running',
7090
- suspension_json = NULL,
7167
+ suspension_json = CASE
7168
+ WHEN $5::boolean AND target.suspension_json ->> 'kind' = 'detached_runner'
7169
+ THEN target.suspension_json ELSE NULL END,
7091
7170
  wait_deadline = NULL,
7092
7171
  updated_at = now()
7093
7172
  FROM waiting_run
@@ -7263,7 +7342,7 @@ export async function claimPostgresSchedulerOverdueDetachedRunnerCeilings(
7263
7342
  JOIN ${tableName('attempts', options)} AS attempts
7264
7343
  ON attempts.run_id = runs.run_id
7265
7344
  AND attempts.attempt = (runs.suspension_json ->> 'runnerAttempt')::integer
7266
- WHERE runs.status = 'waiting'
7345
+ WHERE runs.status IN ('running', 'waiting')
7267
7346
  AND runs.suspension_json ->> 'kind' = 'detached_runner'
7268
7347
  AND COALESCE(
7269
7348
  runs.wait_deadline,
@@ -7277,7 +7356,7 @@ export async function claimPostgresSchedulerOverdueDetachedRunnerCeilings(
7277
7356
  ELSE NULL
7278
7357
  END
7279
7358
  ) <= clock_timestamp()
7280
- AND attempts.status = 'waiting'
7359
+ AND attempts.status IN ('running', 'waiting')
7281
7360
  AND attempts.lease_token IS NOT NULL
7282
7361
  AND (
7283
7362
  attempts.runner_result_json IS NULL
@@ -7317,7 +7396,7 @@ export async function claimPostgresSchedulerOverdueDetachedRunnerCeilings(
7317
7396
  FROM candidates
7318
7397
  WHERE target.run_id = candidates.run_id
7319
7398
  AND target.attempt = candidates.attempt
7320
- AND target.status = 'waiting'
7399
+ AND target.status IN ('running', 'waiting')
7321
7400
  AND target.lease_token IS NOT NULL
7322
7401
  AND (
7323
7402
  target.runner_result_json IS NULL
@@ -7328,12 +7407,13 @@ export async function claimPostgresSchedulerOverdueDetachedRunnerCeilings(
7328
7407
  running_runs AS (
7329
7408
  UPDATE ${tableName('runs', options)} AS target
7330
7409
  SET status = 'running',
7331
- suspension_json = NULL,
7332
- wait_deadline = NULL,
7410
+ -- A controller can disappear between this claim and the terminal
7411
+ -- write. Retain the detached snapshot and ceiling so its successor
7412
+ -- can reclaim the same fenced obligation without restarting code.
7333
7413
  updated_at = now()
7334
7414
  FROM running_attempts
7335
7415
  WHERE target.run_id = running_attempts.run_id
7336
- AND target.status = 'waiting'
7416
+ AND target.status IN ('running', 'waiting')
7337
7417
  RETURNING target.run_id
7338
7418
  ),
7339
7419
  resumed_outbox AS (
@@ -10573,6 +10653,73 @@ export async function completePostgresSchedulerComputeBillingSessionBootstrap(
10573
10653
  return completed.rows.length > 0;
10574
10654
  }
10575
10655
 
10656
+ /** Deletion needs terminal/cancellation/retired-resource authority. An old
10657
+ * worker error (including a billing end) is not evidence the runner stopped. */
10658
+ function protectedDetachedSandboxSql(
10659
+ identity: {
10660
+ runId: string;
10661
+ provider: string;
10662
+ sandboxId: string;
10663
+ routingDomain: string;
10664
+ },
10665
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
10666
+ ): string {
10667
+ return `EXISTS (
10668
+ SELECT 1 FROM ${tableName('runs', options)} AS detached_run
10669
+ JOIN ${tableName('attempts', options)} AS detached_attempt
10670
+ ON detached_attempt.run_id = detached_run.run_id
10671
+ CROSS JOIN LATERAL jsonb_array_elements(
10672
+ CASE WHEN jsonb_typeof(detached_attempt.resource_json) = 'array'
10673
+ THEN detached_attempt.resource_json
10674
+ WHEN detached_attempt.resource_json IS NULL THEN '[]'::jsonb
10675
+ ELSE jsonb_build_array(detached_attempt.resource_json) END
10676
+ ) AS detached_resource
10677
+ WHERE detached_run.run_id = ${identity.runId}
10678
+ AND detached_run.status IN ('running', 'waiting')
10679
+ AND detached_attempt.status IN ('claimed', 'running', 'waiting')
10680
+ AND detached_attempt.runner_result_json IS NULL
10681
+ AND detached_resource ->> 'sandboxId' = ${identity.sandboxId}
10682
+ AND CASE detached_resource ->> 'kind'
10683
+ WHEN 'daytona_sandbox' THEN 'daytona'
10684
+ WHEN 'modal_sandbox' THEN 'modal' END = ${identity.provider}
10685
+ AND (CASE detached_resource ->> 'kind'
10686
+ WHEN 'modal_sandbox' THEN NULLIF(detached_resource ->> 'modalAppId', '')
10687
+ ELSE NULLIF(detached_resource ->> 'daytonaOrganizationId', '') END)
10688
+ IS NOT DISTINCT FROM ${identity.routingDomain}
10689
+ AND (
10690
+ (detached_run.suspension_json ->> 'kind' = 'detached_runner'
10691
+ AND detached_run.suspension_json -> 'runnerAttempt' = to_jsonb(detached_attempt.attempt)
10692
+ AND detached_run.suspension_json ->> 'sandboxId' = detached_resource ->> 'sandboxId')
10693
+ OR (detached_run.suspension_json IS NULL
10694
+ AND detached_resource -> 'executionDetachedAt' IS NOT NULL
10695
+ AND detached_resource -> 'executionDetachedAt' <> 'null'::jsonb)
10696
+ )
10697
+ AND NOT EXISTS (SELECT 1 FROM ${tableName('attempts', options)} AS newer
10698
+ WHERE newer.run_id = detached_run.run_id
10699
+ AND newer.attempt > detached_attempt.attempt
10700
+ AND newer.status IN ('claimed', 'running', 'waiting', 'completed', 'failed'))
10701
+ AND NOT EXISTS (SELECT 1 FROM ${tableName('control', options)} AS cancel
10702
+ WHERE cancel.key = 'runtime_cancel:' || detached_run.run_id)
10703
+ )`;
10704
+ }
10705
+
10706
+ function protectedDetachedResourceSql(
10707
+ runId: string,
10708
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
10709
+ ): string {
10710
+ return protectedDetachedSandboxSql(
10711
+ {
10712
+ runId,
10713
+ provider:
10714
+ "(CASE resource->>'kind' WHEN 'modal_sandbox' THEN 'modal' ELSE 'daytona' END)",
10715
+ sandboxId: "resource->>'sandboxId'",
10716
+ routingDomain:
10717
+ "(CASE resource->>'kind' WHEN 'modal_sandbox' THEN NULLIF(resource->>'modalAppId', '') ELSE NULLIF(resource->>'daytonaOrganizationId', '') END)",
10718
+ },
10719
+ options,
10720
+ );
10721
+ }
10722
+
10576
10723
  export type PostgresSchedulerSandboxCleanupJob = {
10577
10724
  provider: 'daytona' | 'modal';
10578
10725
  sandboxId: string;
@@ -10636,6 +10783,7 @@ export async function enqueuePostgresSchedulerSandboxCleanupForAttempt(
10636
10783
  ) AS resource
10637
10784
  WHERE resource->>'kind' IN ('daytona_sandbox', 'modal_sandbox')
10638
10785
  AND NULLIF(resource->>'sandboxId', '') IS NOT NULL
10786
+ AND NOT ${protectedDetachedResourceSql('$1', options)}
10639
10787
  ON CONFLICT (provider, sandbox_id, routing_domain_key) DO NOTHING
10640
10788
  RETURNING sandbox_id
10641
10789
  )
@@ -10646,7 +10794,8 @@ export async function enqueuePostgresSchedulerSandboxCleanupForAttempt(
10646
10794
  return enqueued.rows.length;
10647
10795
  }
10648
10796
 
10649
- /** Queue every known sandbox before a requested cancellation can terminalize. */
10797
+ /** Queue eligible current and retired resources; cancellation intent explicitly
10798
+ * admits the current runner before cancellation terminalizes. */
10650
10799
  export async function enqueuePostgresSchedulerSandboxCleanupForRun(
10651
10800
  client: PostgresSchedulerQueryClient,
10652
10801
  input: { runId: string },
@@ -10686,6 +10835,7 @@ export async function enqueuePostgresSchedulerSandboxCleanupForRun(
10686
10835
  AND attempt.resource_json IS NOT NULL
10687
10836
  AND resource->>'kind' IN ('daytona_sandbox', 'modal_sandbox')
10688
10837
  AND NULLIF(resource->>'sandboxId', '') IS NOT NULL
10838
+ AND NOT ${protectedDetachedResourceSql('$1', options)}
10689
10839
  UNION ALL
10690
10840
  SELECT operation.provider, operation.sandbox_id, operation.run_id,
10691
10841
  operation.attempt, operation.environment, operation.routing_domain
@@ -10693,6 +10843,7 @@ export async function enqueuePostgresSchedulerSandboxCleanupForRun(
10693
10843
  WHERE operation.run_id = $1
10694
10844
  AND operation.state IN ('live', 'deleting')
10695
10845
  AND operation.sandbox_id IS NOT NULL
10846
+ AND NOT ${protectedDetachedSandboxSql({ runId: 'operation.run_id', provider: 'operation.provider', sandboxId: 'operation.sandbox_id', routingDomain: 'operation.routing_domain' }, options)}
10696
10847
  ON CONFLICT (provider, sandbox_id, routing_domain_key) DO NOTHING
10697
10848
  `,
10698
10849
  [input.runId],
@@ -10775,6 +10926,7 @@ export async function claimPostgresSchedulerSandboxCleanupJobs(
10775
10926
  client: PostgresSchedulerQueryClient,
10776
10927
  input: {
10777
10928
  leaseOwner: string;
10929
+ runId?: string;
10778
10930
  limit?: number;
10779
10931
  leaseSeconds?: number;
10780
10932
  environment?: string | null;
@@ -10868,6 +11020,8 @@ export async function claimPostgresSchedulerSandboxCleanupJobs(
10868
11020
  AND (cleanup.lease_expires_at IS NULL OR cleanup.lease_expires_at <= now())
10869
11021
  AND ($4::text IS NULL OR cleanup.environment = $4)
10870
11022
  AND ($5::text[] IS NULL OR cleanup.provider = ANY($5::text[]))
11023
+ AND ($8::text IS NULL OR cleanup.run_id = $8)
11024
+ AND NOT ${protectedDetachedSandboxSql({ runId: 'cleanup.run_id', provider: 'cleanup.provider', sandboxId: 'cleanup.sandbox_id', routingDomain: 'cleanup.routing_domain' }, options)}
10871
11025
  ORDER BY
10872
11026
  CASE WHEN cleanup.diagnostic_json IS NOT NULL THEN 0 ELSE 1 END ASC,
10873
11027
  cleanup.available_at ASC,
@@ -10933,7 +11087,7 @@ export async function claimPostgresSchedulerSandboxCleanupJobs(
10933
11087
  attempt_count,
10934
11088
  $1,
10935
11089
  'claimed',
10936
- jsonb_build_object('leaseSeconds', $3::integer)
11090
+ jsonb_build_object('leaseSeconds', $3::integer, 'runStatusAtClaim', terminal_status, 'authorization', 'terminal_cancel_or_retired_resource')
10937
11091
  FROM claimed
10938
11092
  RETURNING provider, sandbox_id, routing_domain
10939
11093
  )
@@ -10952,6 +11106,7 @@ export async function claimPostgresSchedulerSandboxCleanupJobs(
10952
11106
  providers,
10953
11107
  RuntimeSandboxOperationState.Deleting,
10954
11108
  RuntimeSandboxOperationState.Live,
11109
+ input.runId ?? null,
10955
11110
  ],
10956
11111
  );
10957
11112
  return claimed.rows.map((row) => ({
@@ -11599,7 +11754,11 @@ export type PostgresSchedulerCompletedRunnerTerminal = {
11599
11754
 
11600
11755
  export async function listPostgresSchedulerCompletedRunnerTerminals(
11601
11756
  client: PostgresSchedulerQueryClient,
11602
- input: { limit?: number; absurdReleaseId?: string | null } = {},
11757
+ input: {
11758
+ limit?: number;
11759
+ absurdReleaseId?: string | null;
11760
+ includeFailed?: boolean;
11761
+ } = {},
11603
11762
  options?: Pick<PostgresSchedulerOptions, 'schema'>,
11604
11763
  ): Promise<PostgresSchedulerCompletedRunnerTerminal[]> {
11605
11764
  const limit = Math.min(500, Math.max(1, Math.floor(input.limit ?? 100)));
@@ -11618,9 +11777,9 @@ export async function listPostgresSchedulerCompletedRunnerTerminals(
11618
11777
  FROM ${tableName('attempts', options)} AS attempts
11619
11778
  JOIN ${tableName('runs', options)} AS runs
11620
11779
  ON runs.run_id = attempts.run_id
11621
- WHERE attempts.status = 'running'
11780
+ WHERE attempts.status = ANY($4::text[])
11622
11781
  AND attempts.lease_token IS NOT NULL
11623
- AND attempts.runner_result_json ->> 'status' = 'completed'
11782
+ AND attempts.runner_result_json ->> 'status' = ANY($3::text[])
11624
11783
  AND runs.status IN ('queued', 'running', 'waiting')
11625
11784
  AND (
11626
11785
  $1::text IS NULL
@@ -11629,7 +11788,12 @@ export async function listPostgresSchedulerCompletedRunnerTerminals(
11629
11788
  ORDER BY attempts.updated_at ASC, attempts.run_id ASC
11630
11789
  LIMIT $2
11631
11790
  `,
11632
- [input.absurdReleaseId?.trim() || null, limit],
11791
+ [
11792
+ input.absurdReleaseId?.trim() || null,
11793
+ limit,
11794
+ input.includeFailed ? ['completed', 'failed'] : ['completed'],
11795
+ input.includeFailed ? ['running', 'waiting'] : ['running'],
11796
+ ],
11633
11797
  );
11634
11798
  return terminals.rows.map((row) => ({
11635
11799
  runId: row.run_id,
@@ -11727,19 +11891,10 @@ export async function heartbeatPostgresSchedulerDetachedRunner(
11727
11891
  ): Promise<
11728
11892
  'queued' | 'running' | 'waiting' | 'completed' | 'failed' | 'cancelled' | null
11729
11893
  > {
11730
- const heartbeat = await client.query<{ run_id: string }>(
11731
- `
11732
- UPDATE ${tableName('attempts', options)}
11733
- SET heartbeat_at = now(),
11734
- updated_at = now()
11735
- WHERE run_id = $1
11736
- AND attempt = $2
11737
- AND status IN ('claimed', 'running', 'waiting')
11738
- RETURNING run_id
11739
- `,
11740
- [input.runId, input.attempt],
11741
- );
11742
- const run = await client.query<{
11894
+ // Return the status from the same statement that renews this exact attempt.
11895
+ // A separate run read can observe a replacement's waiting state after this
11896
+ // attempt was retired, incorrectly allowing the old runner to execute.
11897
+ const heartbeat = await client.query<{
11743
11898
  status:
11744
11899
  | 'queued'
11745
11900
  | 'running'
@@ -11749,17 +11904,35 @@ export async function heartbeatPostgresSchedulerDetachedRunner(
11749
11904
  | 'cancelled';
11750
11905
  }>(
11751
11906
  `
11907
+ WITH heartbeat AS (
11908
+ UPDATE ${tableName('attempts', options)} AS target
11909
+ SET heartbeat_at = now(),
11910
+ updated_at = now()
11911
+ FROM ${tableName('runs', options)} AS run
11912
+ WHERE target.run_id = $1
11913
+ AND target.attempt = $2
11914
+ AND target.status IN ('claimed', 'running', 'waiting')
11915
+ AND run.run_id = target.run_id
11916
+ AND run.status IN ('queued', 'running', 'waiting')
11917
+ AND NOT EXISTS (
11918
+ SELECT 1
11919
+ FROM ${tableName('attempts', options)} AS newer
11920
+ WHERE newer.run_id = target.run_id
11921
+ AND newer.attempt > target.attempt
11922
+ )
11923
+ RETURNING run.status
11924
+ )
11925
+ SELECT status FROM heartbeat
11926
+ UNION ALL
11752
11927
  SELECT status
11753
11928
  FROM ${tableName('runs', options)}
11754
11929
  WHERE run_id = $1
11930
+ AND status IN ('completed', 'failed', 'cancelled')
11931
+ AND NOT EXISTS (SELECT 1 FROM heartbeat)
11755
11932
  `,
11756
- [input.runId],
11933
+ [input.runId, input.attempt],
11757
11934
  );
11758
- const status = run.rows[0]?.status ?? null;
11759
- if (status === 'completed' || status === 'failed' || status === 'cancelled') {
11760
- return status;
11761
- }
11762
- return heartbeat.rows.length > 0 ? status : null;
11935
+ return heartbeat.rows[0]?.status ?? null;
11763
11936
  }
11764
11937
 
11765
11938
  export async function claimPostgresSchedulerSignal(