deepline 0.3.175 → 0.3.176

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.176',
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
@@ -11991,16 +12002,46 @@ export async function cancelPostgresSchedulerRun(
11991
12002
  AND NULLIF(resource->>'sandboxId', '') IS NOT NULL
11992
12003
  ON CONFLICT (provider, sandbox_id, routing_domain_key) DO NOTHING
11993
12004
  ),
12005
+ cancellation_outbox_head AS MATERIALIZED (
12006
+ SELECT pending.event_id
12007
+ FROM ${tableName('outbox', options)} AS pending
12008
+ JOIN cancelled_run ON cancelled_run.run_id = pending.run_id
12009
+ WHERE pending.delivered_at IS NULL
12010
+ AND pending.failed_at IS NULL
12011
+ ORDER BY ${runLedgerOrderSql('pending')}
12012
+ LIMIT 1
12013
+ ),
12014
+ cancellation_outbox_lease AS (
12015
+ UPDATE ${tableName('outbox', options)} AS pending
12016
+ SET lease_owner = $6::text,
12017
+ lease_expires_at = now() + make_interval(secs => $7::integer),
12018
+ next_attempt_at = NULL
12019
+ WHERE $6::text IS NOT NULL
12020
+ AND pending.event_id IN (SELECT event_id FROM cancellation_outbox_head)
12021
+ RETURNING pending.event_id
12022
+ ),
11994
12023
  terminal_outbox AS (
11995
12024
  INSERT INTO ${tableName('outbox', options)} (
11996
- event_id, run_id, event_type, payload_json, created_at
12025
+ event_id, run_id, event_type, payload_json, created_at,
12026
+ next_attempt_at, lease_owner, lease_expires_at
11997
12027
  )
11998
12028
  SELECT
11999
12029
  'run-terminal:' || run_id,
12000
12030
  cancelled_run.run_id,
12001
12031
  'run.cancelled',
12002
12032
  jsonb_build_object('error', $5::text),
12003
- ${causalOutboxCreatedAt('cancelled_run.run_id', options)}
12033
+ ${causalOutboxCreatedAt('cancelled_run.run_id', options)},
12034
+ NULL,
12035
+ CASE
12036
+ WHEN EXISTS (SELECT 1 FROM cancellation_outbox_head) THEN NULL
12037
+ ELSE $6::text
12038
+ END,
12039
+ CASE
12040
+ WHEN $6::text IS NULL
12041
+ OR EXISTS (SELECT 1 FROM cancellation_outbox_head)
12042
+ THEN NULL
12043
+ ELSE now() + make_interval(secs => $7::integer)
12044
+ END
12004
12045
  FROM cancelled_run
12005
12046
  ON CONFLICT (event_id) DO NOTHING
12006
12047
  ),
@@ -12017,11 +12058,39 @@ export async function cancelPostgresSchedulerRun(
12017
12058
  postgresSchedulerNotifyChannel('terminal', options),
12018
12059
  JSON.stringify({ runId: input.runId, status: 'cancelled' }),
12019
12060
  reason,
12061
+ outboxLeaseOwner,
12062
+ outboxLeaseSeconds,
12020
12063
  ...(strandedFence
12021
12064
  ? [strandedFence.releaseId, strandedFence.absurdTaskId]
12022
12065
  : []),
12023
12066
  ],
12024
12067
  );
12068
+ if (cancelled.rows.length === 0 && outboxLeaseOwner) {
12069
+ // A competing terminal writer may win while this UPDATE waits on the run
12070
+ // row. The next statement gets a fresh READ COMMITTED snapshot. Adopt its
12071
+ // pending causal prefix before this enclosing cancellation transaction
12072
+ // commits so the request can still project the winning terminal state
12073
+ // without waiting for a background lease.
12074
+ await client.query(
12075
+ `UPDATE ${tableName('outbox', options)} AS pending
12076
+ SET lease_owner = $2,
12077
+ lease_expires_at = now() + make_interval(secs => $3::integer),
12078
+ next_attempt_at = NULL
12079
+ WHERE pending.event_id = (
12080
+ SELECT candidate.event_id
12081
+ FROM ${tableName('outbox', options)} AS candidate
12082
+ JOIN ${tableName('runs', options)} AS terminal_run
12083
+ ON terminal_run.run_id = candidate.run_id
12084
+ WHERE candidate.run_id = $1
12085
+ AND candidate.delivered_at IS NULL
12086
+ AND candidate.failed_at IS NULL
12087
+ AND terminal_run.status IN ('completed', 'failed', 'cancelled')
12088
+ ORDER BY ${runLedgerOrderSql('candidate')}
12089
+ LIMIT 1
12090
+ )`,
12091
+ [input.runId, outboxLeaseOwner, outboxLeaseSeconds],
12092
+ );
12093
+ }
12025
12094
  return cancelled.rows.length > 0;
12026
12095
  }
12027
12096
 
@@ -15369,7 +15438,12 @@ export async function readPendingPostgresSchedulerOutboxEvents(
15369
15438
  AND candidate.failed_at IS NULL
15370
15439
  AND coalesce(candidate.next_attempt_at, candidate.created_at) <= now()
15371
15440
  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())
15441
+ AND (
15442
+ $3::text IS NULL
15443
+ OR candidate.lease_owner = $3::text
15444
+ OR candidate.lease_expires_at IS NULL
15445
+ OR candidate.lease_expires_at <= now()
15446
+ )
15373
15447
  -- A producer transaction may commit an older timestamp after a
15374
15448
  -- delivery has started. Ownership covers the run, not just the
15375
15449
  -- current oldest row: that late predecessor must wait as well.
@@ -15379,6 +15453,7 @@ export async function readPendingPostgresSchedulerOutboxEvents(
15379
15453
  AND active.delivered_at IS NULL
15380
15454
  AND active.lease_owner IS NOT NULL
15381
15455
  AND active.lease_expires_at > now()
15456
+ AND active.lease_owner IS DISTINCT FROM $3::text
15382
15457
  )
15383
15458
  -- Competing projectors acquire one run through its oldest event.
15384
15459
  -- Locking this head prevents another projector from splitting the
@@ -15417,7 +15492,12 @@ export async function readPendingPostgresSchedulerOutboxEvents(
15417
15492
  -- its receipt stable independently of the recovery caller's limit.
15418
15493
  AND (outbox.event_type <> 'run.events' OR outbox.event_id = head.event_id)
15419
15494
  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())
15495
+ AND (
15496
+ $3::text IS NULL
15497
+ OR outbox.lease_owner = $3::text
15498
+ OR outbox.lease_expires_at IS NULL
15499
+ OR outbox.lease_expires_at <= now()
15500
+ )
15421
15501
  -- A started terminal batch is deliberately stable across a retry.
15422
15502
  -- Do not enlarge it with a later event before acknowledgement: the
15423
15503
  -- runtime can replay the old idempotent append, but it cannot prove
@@ -15460,6 +15540,7 @@ export async function readPendingPostgresSchedulerOutboxEvents(
15460
15540
  $3::text IS NOT NULL
15461
15541
  AND predecessor.lease_expires_at IS NOT NULL
15462
15542
  AND predecessor.lease_expires_at > now()
15543
+ AND predecessor.lease_owner IS DISTINCT FROM $3::text
15463
15544
  )
15464
15545
  )
15465
15546
  )
@@ -16348,6 +16429,30 @@ export async function listPostgresSchedulerOutboxMaterializationPlans(
16348
16429
  */
16349
16430
  export type RunLedgerOutboxTokenAuthority = 'local_mint' | 'gateway';
16350
16431
 
16432
+ const POSTGRES_SCHEDULER_CANCELLATION_OUTBOX_LEASE_SECONDS = 15;
16433
+
16434
+ /**
16435
+ * Stable lease identity shared by the atomic scheduler cancellation and the
16436
+ * app-side terminal projector. Cancellation can therefore fence a stale or
16437
+ * slow background delivery inside the terminal transaction, then resume from
16438
+ * that exact head immediately without waiting for its old lease to age.
16439
+ */
16440
+ export function postgresSchedulerCancellationOutboxLease(runId: string): {
16441
+ leaseOwner: string;
16442
+ leaseSeconds: number;
16443
+ } {
16444
+ const normalizedRunId = runId.trim();
16445
+ if (!normalizedRunId) {
16446
+ throw new Error('Cancellation outbox lease requires a run id.');
16447
+ }
16448
+ return {
16449
+ leaseOwner: `run-ledger-cancel:${createHash('sha256')
16450
+ .update(normalizedRunId)
16451
+ .digest('hex')}`,
16452
+ leaseSeconds: POSTGRES_SCHEDULER_CANCELLATION_OUTBOX_LEASE_SECONDS,
16453
+ };
16454
+ }
16455
+
16351
16456
  export type RunLedgerOutboxDispatchInput = {
16352
16457
  runtimeApi?: WorkerRuntimeApiContext | null;
16353
16458
  limit?: number;
@@ -16564,10 +16669,11 @@ export async function dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope(
16564
16669
  AND active.delivered_at IS NULL
16565
16670
  AND active.lease_owner IS NOT NULL
16566
16671
  AND active.lease_expires_at > now()
16672
+ AND active.lease_owner IS DISTINCT FROM $2::text
16567
16673
  )
16568
16674
  FOR UPDATE OF runs SKIP LOCKED
16569
16675
  `,
16570
- [targetRunId],
16676
+ [targetRunId, leaseOwner],
16571
16677
  )
16572
16678
  : await client.query<{ run_id: string }>(`
16573
16679
  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.176",
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.176",
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.176",
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.176",
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.176";
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.176";
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.176",
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.176",
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.176",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",