deepline 0.3.175 → 0.3.177

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.
@@ -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.175',
203
+ version: '0.3.177',
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: {
@@ -14,33 +14,27 @@
14
14
  * Persist the absurd task id
15
15
  * on the run row so a later cancel can target it.
16
16
  * observe/result → reuse the postgres LISTEN/NOTIFY run handle.
17
- * cancel → durable Absurd cancellation lane + bounded postgres terminal
18
- * projection (with worker reconciliation when locks are busy).
17
+ * cancel → one bounded transaction cancels the Absurd task and terminalizes
18
+ * the postgres scheduler run. Success means both states committed.
19
19
  * signal → durable signal row (authoritative payload) + absurd emitEvent
20
20
  * (the wakeup only; payload is re-read from the signal row).
21
21
  */
22
22
  import { recordPerfTrace } from '../perf-trace';
23
23
  import { CTX_RUN_PLAY_INLINE_ONLY } from '../child-execution-strategy';
24
24
  import { PLAY_ADMISSION_MAX_ACTIVE_LIFECYCLES } from '../admission-transport-policy';
25
- import {
26
- defaultRuntimeSandboxReconciliation,
27
- runtimeSandboxDeleteSucceeded,
28
- runtimeSandboxRefIdentityKey,
29
- runtimeSandboxRefFromResource,
30
- type RuntimeSandboxRef,
31
- } from '../runner-backends/runtime-sandbox-reconciliation';
32
25
  import {
33
26
  PLAY_SCHEDULER_BACKENDS,
34
- RUNTIME_SCHEDULER_CANCELLATION_PENDING_CODE,
35
27
  type PlaySchedulerBackend,
36
28
  type PlaySchedulerRunHandle,
37
29
  type PlaySchedulerSubmitInput,
38
30
  } from '../scheduler-backend';
39
31
  import {
32
+ cancelPostgresSchedulerRun,
33
+ postgresSchedulerCancellationOutboxLease,
40
34
  postgresSchedulerRunHandle,
41
35
  readPostgresSchedulerLaunch,
42
36
  readPostgresSchedulerAbsurdTaskId,
43
- readPostgresSchedulerRuntimeResourcesForRun,
37
+ readPostgresSchedulerResult,
44
38
  recordPostgresSchedulerSignal,
45
39
  } from './postgres';
46
40
  import {
@@ -57,7 +51,6 @@ import { withRuntimeSchedulerSubmitAdmission } from './submit-admission';
57
51
  import {
58
52
  ABSURD_PLAY_RUN_MAX_ATTEMPTS,
59
53
  ABSURD_PLAY_RUN_RETRY_STRATEGY,
60
- ABSURD_DAYTONA_SANDBOX_CLEANUP_TASK,
61
54
  absurdIntegrationEventName,
62
55
  absurdQueueNameForRelease,
63
56
  absurdReleaseIdForLaunch,
@@ -67,7 +60,6 @@ import {
67
60
  normalizeAbsurdSchedulerOptions,
68
61
  resolveAbsurdPoolOptions,
69
62
  serverlessAdmissionSubmitMaxActive,
70
- type AbsurdDaytonaSandboxCleanupTaskParams,
71
63
  type AbsurdSchedulerOptions,
72
64
  } from './absurd-shared';
73
65
 
@@ -162,168 +154,98 @@ function absurdRunHandle(input: {
162
154
  const runsQueue = absurdQueueNameForRelease(releaseId);
163
155
  const cancellationKey = `${poolOptions.url}::${effectiveOptions.schema ?? ''}::${input.runId}`;
164
156
 
165
- const cancelAbsurdTask = async () => {
166
- const taskId = await withRuntimeSchedulerClient(poolOptions, (client) =>
167
- readPostgresSchedulerAbsurdTaskId(client, input.runId, effectiveOptions),
168
- );
169
- if (!taskId) return;
170
- // Keep cancellation on the same bounded control-plane contract as the
171
- // scheduler terminal write. This is a durable lane transition, not an
172
- // unbounded best-effort query which can pile up behind a worker lock.
173
- await withRuntimeSchedulerTransaction(
157
+ const cancelAbsurdRunSynchronously = async (reason?: string) =>
158
+ withRuntimeSchedulerTransaction(
174
159
  poolOptions,
175
160
  {
176
161
  lockTimeoutMs: RUNTIME_SCHEDULER_CANCEL_LOCK_TIMEOUT_MS,
177
162
  statementTimeoutMs: RUNTIME_SCHEDULER_CANCEL_STATEMENT_TIMEOUT_MS,
178
163
  },
179
164
  async (client) => {
180
- await client.query(`SELECT absurd.cancel_task($1, $2::uuid)`, [
181
- runsQueue,
182
- taskId,
183
- ]);
184
- // Older v4 Absurd functions do not materialize a manual cancellation
185
- // deadline or reason. Stamp both in the adapter so existing queues
186
- // participate in the same durable reconciliation lane without a
187
- // schema cutover. The explicit marker keeps a user cancel distinct
188
- // from a task cancelled by its configured max_delay sweep.
189
- await client.query(
190
- `UPDATE absurd.${quotePostgresIdentifier(`t_${runsQueue}`)}
165
+ // Resolve the immutable release lane inside the same checkout and
166
+ // transaction that performs cancellation. The stop endpoint therefore
167
+ // has no preliminary lane lookup that can succeed or fail separately.
168
+ const launch = await readPostgresSchedulerLaunch(
169
+ client,
170
+ input.runId,
171
+ effectiveOptions,
172
+ { caller: 'play_run_control' },
173
+ );
174
+ if (!launch) {
175
+ throw new Error(
176
+ `Cannot cancel play run "${input.runId}": scheduler launch is missing.`,
177
+ );
178
+ }
179
+ const cancellationReleaseId = absurdReleaseIdForLaunch(launch);
180
+ const cancellationQueue = absurdQueueNameForRelease(
181
+ cancellationReleaseId,
182
+ );
183
+ const taskId = await readPostgresSchedulerAbsurdTaskId(
184
+ client,
185
+ input.runId,
186
+ effectiveOptions,
187
+ );
188
+ if (taskId) {
189
+ await client.query(`SELECT absurd.cancel_task($1, $2::uuid)`, [
190
+ cancellationQueue,
191
+ taskId,
192
+ ]);
193
+ // Older v4 Absurd functions do not materialize a manual cancellation
194
+ // deadline or reason. Stamp both in the adapter so existing queues
195
+ // participate in the same durable reconciliation lane without a
196
+ // schema cutover. The explicit marker keeps a user cancel distinct
197
+ // from a task cancelled by its configured max_delay sweep.
198
+ await client.query(
199
+ `UPDATE absurd.${quotePostgresIdentifier(`t_${cancellationQueue}`)}
191
200
  SET cancel_at = COALESCE(cancel_at, clock_timestamp()),
192
201
  cancellation = COALESCE(cancellation, '{}'::jsonb)
193
202
  || jsonb_build_object('manual', true)
194
203
  WHERE task_id = $1::uuid
195
204
  AND state = 'cancelled'`,
196
- [taskId],
205
+ [taskId],
206
+ );
207
+ }
208
+ const cancelled = await cancelPostgresSchedulerRun(
209
+ client,
210
+ {
211
+ runId: input.runId,
212
+ reason,
213
+ outboxLease: postgresSchedulerCancellationOutboxLease(input.runId),
214
+ },
215
+ effectiveOptions,
197
216
  );
217
+ // The update returning a row is proof that this transaction wrote the
218
+ // terminal state and outbox event. Only the idempotent retry/race path
219
+ // needs one read to prove another terminal writer already won.
220
+ if (!cancelled) {
221
+ const terminal = await readPostgresSchedulerResult(
222
+ client,
223
+ input.runId,
224
+ effectiveOptions,
225
+ );
226
+ if (!terminal) {
227
+ throw new Error(
228
+ `Cancellation did not terminalize scheduler run "${input.runId}".`,
229
+ );
230
+ }
231
+ }
198
232
  },
199
233
  {
200
234
  runId: input.runId,
201
- phase: 'cancel_engine_task',
235
+ phase: 'cancel_run_synchronously',
202
236
  priority: 'critical',
203
237
  },
204
- { maxAttempts: 1 },
205
238
  );
206
- };
207
-
208
- const cancellationPendingError = (cause: unknown): Error => {
209
- const error = new Error(
210
- `Run cancellation was durably requested, but scheduler terminalization is pending for ${input.runId}.`,
211
- );
212
- Object.assign(error, {
213
- code: RUNTIME_SCHEDULER_CANCELLATION_PENDING_CODE,
214
- cause,
215
- });
216
- return error;
217
- };
218
239
 
219
240
  const requestCancellation = async (reason?: string) => {
220
241
  await runCancellationSingleFlight(cancellationKey, async () => {
221
- // First move the durable engine task into its cancellation lane. If the
222
- // scheduler tables are briefly locked, the worker reconciler can finish
223
- // the terminal write later without another user request piling up.
224
- await cancelAbsurdTask();
225
- try {
226
- await delegate.cancel(reason);
227
- } catch (error) {
228
- throw cancellationPendingError(error);
229
- }
230
- try {
231
- await cleanupPersistedRuntimeSandboxes();
232
- } catch (error) {
233
- // The scheduler terminal transaction already won. Its cleanup jobs are
234
- // durable, so a best-effort eager cleanup failure must not turn a real
235
- // cancellation into a false 502 or cause the caller to retry the
236
- // terminal write.
237
- console.warn('[absurd.runtime_sandbox_cleanup_deferred]', {
238
- runId: input.runId,
239
- error: error instanceof Error ? error.message : String(error),
240
- });
241
- }
242
+ // Engine cancellation and the scheduler terminal write commit together.
243
+ // A timeout rolls both back, so returning means the run is terminal and
244
+ // throwing means the caller can safely retry one unchanged operation.
245
+ await cancelAbsurdRunSynchronously(reason);
242
246
  });
243
247
  };
244
248
 
245
- const cleanupPersistedRuntimeSandboxes = async () => {
246
- const resources = await withRuntimeSchedulerClient(poolOptions, (client) =>
247
- readPostgresSchedulerRuntimeResourcesForRun(
248
- client,
249
- { runId: input.runId },
250
- effectiveOptions,
251
- ),
252
- );
253
- const productionRefs = new Map<string, RuntimeSandboxRef>();
254
- const workerOwnedRefs = new Map<string, RuntimeSandboxRef>();
255
- for (const resource of resources) {
256
- if (
257
- !resource ||
258
- typeof resource !== 'object' ||
259
- Array.isArray(resource)
260
- ) {
261
- continue;
262
- }
263
- const record = resource as Record<string, unknown>;
264
- const ref = runtimeSandboxRefFromResource(record);
265
- if (!ref) continue;
266
- const refKey = runtimeSandboxRefIdentityKey(ref);
267
- // Modal credentials and all preview provider credentials are worker-only.
268
- // This explicit task keeps deletion on the owning release lane. Legacy
269
- // Daytona records predate runtimeEnvironment, so retain their marker.
270
- const runtimeEnvironment =
271
- record.runtimeEnvironment === 'preview' ||
272
- record.daytonaEnvironment === 'preview'
273
- ? 'preview'
274
- : 'production';
275
- if (runtimeEnvironment === 'preview' || ref.provider === 'modal') {
276
- workerOwnedRefs.set(refKey, ref);
277
- continue;
278
- }
279
- productionRefs.set(refKey, ref);
280
- }
281
- if (workerOwnedRefs.size > 0) {
282
- const absurd = createAbsurdRuntimeClient(poolOptions, runsQueue);
283
- await Promise.all(
284
- [...workerOwnedRefs.values()].map((runtimeSandboxRef) => {
285
- // sandboxId remains in the payload for workers deployed before the
286
- // provider-scoped cleanup cutover. New workers use the versioned ref.
287
- const params: AbsurdDaytonaSandboxCleanupTaskParams & {
288
- runtimeSandboxRef: RuntimeSandboxRef;
289
- } = {
290
- runId: input.runId,
291
- sandboxId: runtimeSandboxRef.resourceId,
292
- runtimeSandboxRef,
293
- };
294
- return absurd.spawn(ABSURD_DAYTONA_SANDBOX_CLEANUP_TASK, params, {
295
- queue: runsQueue,
296
- idempotencyKey: `sandbox-cleanup:${input.runId}:${runtimeSandboxRefIdentityKey(runtimeSandboxRef)}`,
297
- maxAttempts: ABSURD_PLAY_RUN_MAX_ATTEMPTS,
298
- retryStrategy: ABSURD_PLAY_RUN_RETRY_STRATEGY,
299
- });
300
- }),
301
- );
302
- }
303
- if (productionRefs.size === 0) return;
304
- const cleanupResults = await Promise.allSettled(
305
- [...productionRefs.values()].map(async (ref) => {
306
- const outcome = await defaultRuntimeSandboxReconciliation.delete(ref);
307
- if (!runtimeSandboxDeleteSucceeded(outcome)) {
308
- throw new Error(
309
- `${ref.provider} sandbox cleanup did not delete ${ref.resourceId}.`,
310
- );
311
- }
312
- }),
313
- );
314
- for (const result of cleanupResults) {
315
- if (result.status === 'rejected') {
316
- console.warn('[absurd.runtime_sandbox_cleanup_failed]', {
317
- runId: input.runId,
318
- error:
319
- result.reason instanceof Error
320
- ? result.reason.message
321
- : String(result.reason),
322
- });
323
- }
324
- }
325
- };
326
-
327
249
  return {
328
250
  ...delegate,
329
251
  runId: input.runId,
@@ -11852,15 +11852,26 @@ export async function cancelPostgresSchedulerRun(
11852
11852
  absurdTaskId: string;
11853
11853
  engineTaskTable: string | null;
11854
11854
  };
11855
+ /**
11856
+ * Optional control-plane ownership for the pending causal head. The
11857
+ * caller resumes this same lease after commit to make cancellation visible
11858
+ * synchronously in the canonical Run Ledger.
11859
+ */
11860
+ outboxLease?: { leaseOwner: string; leaseSeconds: number };
11855
11861
  },
11856
11862
  options?: Pick<PostgresSchedulerOptions, 'schema'>,
11857
11863
  ): Promise<boolean> {
11858
11864
  const reason = input.reason?.trim() || 'Cancelled.';
11865
+ const outboxLeaseOwner = input.outboxLease?.leaseOwner.trim() || null;
11866
+ const outboxLeaseSeconds = Math.max(
11867
+ 5,
11868
+ Math.min(300, Math.floor(input.outboxLease?.leaseSeconds ?? 15)),
11869
+ );
11859
11870
  const strandedFence = input.onlyIfStrandedPreExecution;
11860
11871
  const strandedEligibility = strandedFence
11861
11872
  ? `AND target.status = 'queued'
11862
- AND target.launch_json->>'absurdReleaseId' = $6
11863
- AND target.absurd_task_id = $7
11873
+ AND target.launch_json->>'absurdReleaseId' = $8
11874
+ AND target.absurd_task_id = $9
11864
11875
  AND NOT EXISTS (
11865
11876
  SELECT 1 FROM ${tableName('attempts', options)} AS attempt
11866
11877
  WHERE attempt.run_id = target.run_id
@@ -11903,6 +11914,14 @@ export async function cancelPostgresSchedulerRun(
11903
11914
  FROM cancelled_run
11904
11915
  WHERE receipt.org_id = cancelled_run.org_id
11905
11916
  AND receipt.status IN ('queued', 'running')
11917
+ -- Every successful claim stamps both run_id and
11918
+ -- lease_owner_run_id with the current owner. Keep the explicit
11919
+ -- owner fence below for mixed-version rows, while giving Postgres
11920
+ -- the direct run_id equality it needs to use
11921
+ -- runtime_scheduler_receipts_run_idx. Without this predicate,
11922
+ -- COALESCE forces a scan of the org's complete receipt history and
11923
+ -- cancellation can hit its statement timeout on a large workspace.
11924
+ AND receipt.run_id = cancelled_run.run_id
11906
11925
  AND COALESCE(receipt.lease_owner_run_id, receipt.run_id)
11907
11926
  = cancelled_run.run_id
11908
11927
  RETURNING receipt.k
@@ -11991,16 +12010,46 @@ export async function cancelPostgresSchedulerRun(
11991
12010
  AND NULLIF(resource->>'sandboxId', '') IS NOT NULL
11992
12011
  ON CONFLICT (provider, sandbox_id, routing_domain_key) DO NOTHING
11993
12012
  ),
12013
+ cancellation_outbox_head AS MATERIALIZED (
12014
+ SELECT pending.event_id
12015
+ FROM ${tableName('outbox', options)} AS pending
12016
+ JOIN cancelled_run ON cancelled_run.run_id = pending.run_id
12017
+ WHERE pending.delivered_at IS NULL
12018
+ AND pending.failed_at IS NULL
12019
+ ORDER BY ${runLedgerOrderSql('pending')}
12020
+ LIMIT 1
12021
+ ),
12022
+ cancellation_outbox_lease AS (
12023
+ UPDATE ${tableName('outbox', options)} AS pending
12024
+ SET lease_owner = $6::text,
12025
+ lease_expires_at = now() + make_interval(secs => $7::integer),
12026
+ next_attempt_at = NULL
12027
+ WHERE $6::text IS NOT NULL
12028
+ AND pending.event_id IN (SELECT event_id FROM cancellation_outbox_head)
12029
+ RETURNING pending.event_id
12030
+ ),
11994
12031
  terminal_outbox AS (
11995
12032
  INSERT INTO ${tableName('outbox', options)} (
11996
- event_id, run_id, event_type, payload_json, created_at
12033
+ event_id, run_id, event_type, payload_json, created_at,
12034
+ next_attempt_at, lease_owner, lease_expires_at
11997
12035
  )
11998
12036
  SELECT
11999
12037
  'run-terminal:' || run_id,
12000
12038
  cancelled_run.run_id,
12001
12039
  'run.cancelled',
12002
12040
  jsonb_build_object('error', $5::text),
12003
- ${causalOutboxCreatedAt('cancelled_run.run_id', options)}
12041
+ ${causalOutboxCreatedAt('cancelled_run.run_id', options)},
12042
+ NULL,
12043
+ CASE
12044
+ WHEN EXISTS (SELECT 1 FROM cancellation_outbox_head) THEN NULL
12045
+ ELSE $6::text
12046
+ END,
12047
+ CASE
12048
+ WHEN $6::text IS NULL
12049
+ OR EXISTS (SELECT 1 FROM cancellation_outbox_head)
12050
+ THEN NULL
12051
+ ELSE now() + make_interval(secs => $7::integer)
12052
+ END
12004
12053
  FROM cancelled_run
12005
12054
  ON CONFLICT (event_id) DO NOTHING
12006
12055
  ),
@@ -12017,11 +12066,39 @@ export async function cancelPostgresSchedulerRun(
12017
12066
  postgresSchedulerNotifyChannel('terminal', options),
12018
12067
  JSON.stringify({ runId: input.runId, status: 'cancelled' }),
12019
12068
  reason,
12069
+ outboxLeaseOwner,
12070
+ outboxLeaseSeconds,
12020
12071
  ...(strandedFence
12021
12072
  ? [strandedFence.releaseId, strandedFence.absurdTaskId]
12022
12073
  : []),
12023
12074
  ],
12024
12075
  );
12076
+ if (cancelled.rows.length === 0 && outboxLeaseOwner) {
12077
+ // A competing terminal writer may win while this UPDATE waits on the run
12078
+ // row. The next statement gets a fresh READ COMMITTED snapshot. Adopt its
12079
+ // pending causal prefix before this enclosing cancellation transaction
12080
+ // commits so the request can still project the winning terminal state
12081
+ // without waiting for a background lease.
12082
+ await client.query(
12083
+ `UPDATE ${tableName('outbox', options)} AS pending
12084
+ SET lease_owner = $2,
12085
+ lease_expires_at = now() + make_interval(secs => $3::integer),
12086
+ next_attempt_at = NULL
12087
+ WHERE pending.event_id = (
12088
+ SELECT candidate.event_id
12089
+ FROM ${tableName('outbox', options)} AS candidate
12090
+ JOIN ${tableName('runs', options)} AS terminal_run
12091
+ ON terminal_run.run_id = candidate.run_id
12092
+ WHERE candidate.run_id = $1
12093
+ AND candidate.delivered_at IS NULL
12094
+ AND candidate.failed_at IS NULL
12095
+ AND terminal_run.status IN ('completed', 'failed', 'cancelled')
12096
+ ORDER BY ${runLedgerOrderSql('candidate')}
12097
+ LIMIT 1
12098
+ )`,
12099
+ [input.runId, outboxLeaseOwner, outboxLeaseSeconds],
12100
+ );
12101
+ }
12025
12102
  return cancelled.rows.length > 0;
12026
12103
  }
12027
12104
 
@@ -15369,7 +15446,12 @@ export async function readPendingPostgresSchedulerOutboxEvents(
15369
15446
  AND candidate.failed_at IS NULL
15370
15447
  AND coalesce(candidate.next_attempt_at, candidate.created_at) <= now()
15371
15448
  AND ($2::text IS NULL OR candidate.run_id = $2)
15372
- AND ($3::text IS NULL OR candidate.lease_expires_at IS NULL OR candidate.lease_expires_at <= now())
15449
+ AND (
15450
+ $3::text IS NULL
15451
+ OR candidate.lease_owner = $3::text
15452
+ OR candidate.lease_expires_at IS NULL
15453
+ OR candidate.lease_expires_at <= now()
15454
+ )
15373
15455
  -- A producer transaction may commit an older timestamp after a
15374
15456
  -- delivery has started. Ownership covers the run, not just the
15375
15457
  -- current oldest row: that late predecessor must wait as well.
@@ -15379,6 +15461,7 @@ export async function readPendingPostgresSchedulerOutboxEvents(
15379
15461
  AND active.delivered_at IS NULL
15380
15462
  AND active.lease_owner IS NOT NULL
15381
15463
  AND active.lease_expires_at > now()
15464
+ AND active.lease_owner IS DISTINCT FROM $3::text
15382
15465
  )
15383
15466
  -- Competing projectors acquire one run through its oldest event.
15384
15467
  -- Locking this head prevents another projector from splitting the
@@ -15417,7 +15500,12 @@ export async function readPendingPostgresSchedulerOutboxEvents(
15417
15500
  -- its receipt stable independently of the recovery caller's limit.
15418
15501
  AND (outbox.event_type <> 'run.events' OR outbox.event_id = head.event_id)
15419
15502
  AND coalesce(outbox.next_attempt_at, outbox.created_at) <= now()
15420
- AND ($3::text IS NULL OR outbox.lease_expires_at IS NULL OR outbox.lease_expires_at <= now())
15503
+ AND (
15504
+ $3::text IS NULL
15505
+ OR outbox.lease_owner = $3::text
15506
+ OR outbox.lease_expires_at IS NULL
15507
+ OR outbox.lease_expires_at <= now()
15508
+ )
15421
15509
  -- A started terminal batch is deliberately stable across a retry.
15422
15510
  -- Do not enlarge it with a later event before acknowledgement: the
15423
15511
  -- runtime can replay the old idempotent append, but it cannot prove
@@ -15460,6 +15548,7 @@ export async function readPendingPostgresSchedulerOutboxEvents(
15460
15548
  $3::text IS NOT NULL
15461
15549
  AND predecessor.lease_expires_at IS NOT NULL
15462
15550
  AND predecessor.lease_expires_at > now()
15551
+ AND predecessor.lease_owner IS DISTINCT FROM $3::text
15463
15552
  )
15464
15553
  )
15465
15554
  )
@@ -16348,6 +16437,30 @@ export async function listPostgresSchedulerOutboxMaterializationPlans(
16348
16437
  */
16349
16438
  export type RunLedgerOutboxTokenAuthority = 'local_mint' | 'gateway';
16350
16439
 
16440
+ const POSTGRES_SCHEDULER_CANCELLATION_OUTBOX_LEASE_SECONDS = 15;
16441
+
16442
+ /**
16443
+ * Stable lease identity shared by the atomic scheduler cancellation and the
16444
+ * app-side terminal projector. Cancellation can therefore fence a stale or
16445
+ * slow background delivery inside the terminal transaction, then resume from
16446
+ * that exact head immediately without waiting for its old lease to age.
16447
+ */
16448
+ export function postgresSchedulerCancellationOutboxLease(runId: string): {
16449
+ leaseOwner: string;
16450
+ leaseSeconds: number;
16451
+ } {
16452
+ const normalizedRunId = runId.trim();
16453
+ if (!normalizedRunId) {
16454
+ throw new Error('Cancellation outbox lease requires a run id.');
16455
+ }
16456
+ return {
16457
+ leaseOwner: `run-ledger-cancel:${createHash('sha256')
16458
+ .update(normalizedRunId)
16459
+ .digest('hex')}`,
16460
+ leaseSeconds: POSTGRES_SCHEDULER_CANCELLATION_OUTBOX_LEASE_SECONDS,
16461
+ };
16462
+ }
16463
+
16351
16464
  export type RunLedgerOutboxDispatchInput = {
16352
16465
  runtimeApi?: WorkerRuntimeApiContext | null;
16353
16466
  limit?: number;
@@ -16564,10 +16677,11 @@ export async function dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope(
16564
16677
  AND active.delivered_at IS NULL
16565
16678
  AND active.lease_owner IS NOT NULL
16566
16679
  AND active.lease_expires_at > now()
16680
+ AND active.lease_owner IS DISTINCT FROM $2::text
16567
16681
  )
16568
16682
  FOR UPDATE OF runs SKIP LOCKED
16569
16683
  `,
16570
- [targetRunId],
16684
+ [targetRunId, leaseOwner],
16571
16685
  )
16572
16686
  : await client.query<{ run_id: string }>(`
16573
16687
  WITH heads AS MATERIALIZED (
package/dist/cli/index.js CHANGED
@@ -3051,7 +3051,7 @@ var SDK_RELEASE = {
3051
3051
  // getters keep their established compatibility behavior.
3052
3052
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3053
3053
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3054
- version: "0.3.175",
3054
+ version: "0.3.177",
3055
3055
  updateSummary: "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.",
3056
3056
  packageCapabilities: {
3057
3057
  updatePreferences: 1
@@ -3046,7 +3046,7 @@ var SDK_RELEASE = {
3046
3046
  // getters keep their established compatibility behavior.
3047
3047
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3048
3048
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3049
- version: "0.3.175",
3049
+ version: "0.3.177",
3050
3050
  updateSummary: "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.",
3051
3051
  packageCapabilities: {
3052
3052
  updatePreferences: 1
package/dist/index.js CHANGED
@@ -864,7 +864,7 @@ var SDK_RELEASE = {
864
864
  // getters keep their established compatibility behavior.
865
865
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
866
866
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
867
- version: "0.3.175",
867
+ version: "0.3.177",
868
868
  updateSummary: "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.",
869
869
  packageCapabilities: {
870
870
  updatePreferences: 1
package/dist/index.mjs CHANGED
@@ -768,7 +768,7 @@ var SDK_RELEASE = {
768
768
  // getters keep their established compatibility behavior.
769
769
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
770
770
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
771
- version: "0.3.175",
771
+ version: "0.3.177",
772
772
  updateSummary: "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.",
773
773
  packageCapabilities: {
774
774
  updatePreferences: 1
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.175";
152
+ readonly version: "0.3.177";
153
153
  readonly updateSummary: "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.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.d.ts CHANGED
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.175";
152
+ readonly version: "0.3.177";
153
153
  readonly updateSummary: "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.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.js CHANGED
@@ -74,7 +74,7 @@ var SDK_RELEASE = {
74
74
  // getters keep their established compatibility behavior.
75
75
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
76
76
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
77
- version: "0.3.175",
77
+ version: "0.3.177",
78
78
  updateSummary: "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.",
79
79
  packageCapabilities: {
80
80
  updatePreferences: 1
package/dist/release.mjs CHANGED
@@ -48,7 +48,7 @@ var SDK_RELEASE = {
48
48
  // getters keep their established compatibility behavior.
49
49
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
50
50
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
51
- version: "0.3.175",
51
+ version: "0.3.177",
52
52
  updateSummary: "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.",
53
53
  packageCapabilities: {
54
54
  updatePreferences: 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.3.175",
3
+ "version": "0.3.177",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",