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.
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +73 -151
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +113 -7
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
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 →
|
|
18
|
-
*
|
|
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
|
-
|
|
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
|
|
166
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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: '
|
|
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
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
await
|
|
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' = $
|
|
11863
|
-
AND target.absurd_task_id = $
|
|
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 (
|
|
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 (
|
|
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.
|
|
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
|
package/dist/cli/index.mjs
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
package/dist/release.d.mts
CHANGED
|
@@ -149,7 +149,7 @@ type SdkRelease = {
|
|
|
149
149
|
supportPolicy: SdkSupportPolicy;
|
|
150
150
|
};
|
|
151
151
|
declare const SDK_RELEASE: {
|
|
152
|
-
readonly version: "0.3.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|