deepline 0.1.199 → 0.1.200
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/apps/play-runner-workers/src/coordinator-entry.ts +5 -1
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +51 -17
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/README.md +1 -1
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +16 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +40 -9
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +108 -39
- package/dist/bundling-sources/sdk/src/http.ts +5 -2
- package/dist/bundling-sources/sdk/src/play.ts +1 -1
- package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +7 -0
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/worker-play-entry.ts +11 -55
- package/dist/bundling-sources/shared_libs/play-runtime/adaptive-tool-dispatcher.ts +355 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +172 -73
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1794 -377
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +67 -4
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +161 -39
- package/dist/bundling-sources/shared_libs/play-runtime/governor/adaptive-admission.ts +218 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +115 -25
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +17 -17
- package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +22 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +2 -1
- package/dist/bundling-sources/shared_libs/play-runtime/live-events.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +265 -0
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +135 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-input.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +0 -3
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +10 -1
- package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +9 -15
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +262 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-heartbeat-supervisor.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +76 -8
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-state-sink.ts +59 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +269 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +2 -3
- package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +433 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +227 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +882 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +573 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +264 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/index.ts +33 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runner-events.ts +35 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +53 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +459 -103
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-infra-mode.ts +23 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +23 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +16 -24
- package/dist/bundling-sources/shared_libs/play-runtime/serial-task-queue.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/submit-limits.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +81 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +54 -9
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +17 -3
- package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +0 -20
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +105 -4
- package/dist/bundling-sources/shared_libs/plays/row-identity.ts +4 -11
- package/dist/cli/index.js +167 -54
- package/dist/cli/index.mjs +167 -54
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -8
- package/dist/index.mjs +8 -8
- package/dist/plays/bundle-play-file.mjs +15 -20
- package/package.json +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-diagnosis.ts +0 -321
- package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-target.ts +0 -158
- package/dist/bundling-sources/shared_libs/temporal/constants.ts +0 -39
- package/dist/bundling-sources/shared_libs/temporal/preview-config.ts +0 -150
|
@@ -66,10 +66,10 @@ import {
|
|
|
66
66
|
import { RECEIPT_STATUS_CODE, receiptStatusFromCode } from './receipt-status';
|
|
67
67
|
import {
|
|
68
68
|
workReceiptClaimableStatusCodes,
|
|
69
|
+
workReceiptActiveOwnerPredicateSql,
|
|
69
70
|
workReceiptClaimConflictPredicateSql,
|
|
70
71
|
workReceiptHeartbeatPredicateSql,
|
|
71
72
|
workReceiptReleaseOwnerPredicateSql,
|
|
72
|
-
workReceiptRunningOwnerPredicateSql,
|
|
73
73
|
} from './receipt-sql';
|
|
74
74
|
import {
|
|
75
75
|
activeRuntimeSheetAttemptFenceSql,
|
|
@@ -105,6 +105,7 @@ import {
|
|
|
105
105
|
PLAY_RUNTIME_TEST_FAULT_HEADER,
|
|
106
106
|
type RuntimeTestFaultName,
|
|
107
107
|
} from './test-runtime-seams';
|
|
108
|
+
import { vercelProtectionBypassHeader } from './vercel-protection';
|
|
108
109
|
|
|
109
110
|
type RuntimeApiContext = {
|
|
110
111
|
baseUrl?: string | null;
|
|
@@ -302,10 +303,11 @@ const RUNTIME_DB_SESSION_RENEWAL_WINDOW_MS = 60_000;
|
|
|
302
303
|
const RUNTIME_API_RETRY_DELAYS_MS = [
|
|
303
304
|
250, 500, 1_000, 2_000, 4_000, 8_000, 8_000,
|
|
304
305
|
] as const;
|
|
305
|
-
const
|
|
306
|
+
const RUNTIME_API_RESOLVE_PLAY_RETRY_DELAYS_MS = [
|
|
307
|
+
250, 500, 1_000, 2_000, 4_000, 8_000, 8_000, 15_000, 15_000, 30_000,
|
|
308
|
+
] as const;
|
|
306
309
|
const RUNTIME_API_DEFAULT_RETRY_AFTER_MS = 2_000;
|
|
307
310
|
const RUNTIME_API_REQUEST_TIMEOUT_MS = 30_000;
|
|
308
|
-
const vercelProtectionCookieCache = new Map<string, Promise<string | null>>();
|
|
309
311
|
const RUNTIME_POSTGRES_PREWARM_MAX_ATTEMPTS = 4;
|
|
310
312
|
const RUNTIME_POSTGRES_PREWARM_RETRY_DELAYS_MS = [250, 750, 1_500] as const;
|
|
311
313
|
const RUNTIME_POSTGRES_CONNECT_MAX_ATTEMPTS = 4;
|
|
@@ -315,11 +317,12 @@ const RUNTIME_WORK_RECEIPT_QUERY_RETRY_DELAYS_MS = [250, 750] as const;
|
|
|
315
317
|
// Runtime DB sessions are minted against the pooled tenant endpoint. A healthy
|
|
316
318
|
// connect is sub-second; spending minutes on one sandbox dial only hides a
|
|
317
319
|
// broken route and stalls the whole play. Keep retries bounded and loud.
|
|
318
|
-
const RUNTIME_POSTGRES_CONNECT_TIMEOUT_MS =
|
|
320
|
+
const RUNTIME_POSTGRES_CONNECT_TIMEOUT_MS = 10_000;
|
|
319
321
|
// Daytona executes one play per runner process. This is the bounded data-plane
|
|
320
322
|
// budget for that one run's sheet reads/writes, receipts, and terminal/export
|
|
321
323
|
// reads over the PgBouncer URL; it is not a scheduler/control-plane fan-out.
|
|
322
324
|
const RUNTIME_POSTGRES_POOL_MAX_CONNECTIONS = 8;
|
|
325
|
+
const RECEIPT_STATUS_QUEUED = RECEIPT_STATUS_CODE.queued;
|
|
323
326
|
const RECEIPT_STATUS_PENDING = RECEIPT_STATUS_CODE.pending;
|
|
324
327
|
const RECEIPT_STATUS_RUNNING = RECEIPT_STATUS_CODE.running;
|
|
325
328
|
const RECEIPT_STATUS_COMPLETED = RECEIPT_STATUS_CODE.completed;
|
|
@@ -456,20 +459,12 @@ function resolveRuntimeApiUrl(context: RuntimeApiContext): string {
|
|
|
456
459
|
if (!baseUrl) {
|
|
457
460
|
throw new Error('Runner runtime API requires a baseUrl.');
|
|
458
461
|
}
|
|
459
|
-
|
|
460
|
-
`${baseUrl.replace(/\/$/, '')}${PLAY_RUNTIME_API_COMPAT_PATH}`,
|
|
461
|
-
);
|
|
462
|
-
const bypassToken = context.vercelProtectionBypassToken?.trim();
|
|
463
|
-
if (bypassToken) {
|
|
464
|
-
url.searchParams.set('x-vercel-set-bypass-cookie', 'true');
|
|
465
|
-
url.searchParams.set('x-vercel-protection-bypass', bypassToken);
|
|
466
|
-
}
|
|
467
|
-
return url.toString();
|
|
462
|
+
return `${baseUrl.replace(/\/$/, '')}${PLAY_RUNTIME_API_COMPAT_PATH}`;
|
|
468
463
|
}
|
|
469
464
|
|
|
470
465
|
function resolveRuntimeApiHeaders(
|
|
471
466
|
context: RuntimeApiContext,
|
|
472
|
-
|
|
467
|
+
vercelHeaders: Record<string, string>,
|
|
473
468
|
): Record<string, string> {
|
|
474
469
|
const token = context.executorToken?.trim();
|
|
475
470
|
if (!token) {
|
|
@@ -479,61 +474,16 @@ function resolveRuntimeApiHeaders(
|
|
|
479
474
|
'content-type': 'application/json',
|
|
480
475
|
authorization: `Bearer ${token}`,
|
|
481
476
|
[PLAY_RUNTIME_CONTRACT_HEADER]: String(PLAY_RUNTIME_CONTRACT),
|
|
482
|
-
...
|
|
483
|
-
? { 'x-vercel-protection-bypass': context.vercelProtectionBypassToken }
|
|
484
|
-
: {}),
|
|
477
|
+
...vercelHeaders,
|
|
485
478
|
...(context.runtimeTestFaultHeader?.trim()
|
|
486
479
|
? {
|
|
487
480
|
[PLAY_RUNTIME_TEST_FAULT_HEADER]:
|
|
488
481
|
context.runtimeTestFaultHeader.trim(),
|
|
489
482
|
}
|
|
490
483
|
: {}),
|
|
491
|
-
...(vercelProtectionCookie ? { cookie: vercelProtectionCookie } : {}),
|
|
492
484
|
};
|
|
493
485
|
}
|
|
494
486
|
|
|
495
|
-
function setCookieHeaders(headers: Headers): string[] {
|
|
496
|
-
const getter = (headers as Headers & { getSetCookie?: () => string[] })
|
|
497
|
-
.getSetCookie;
|
|
498
|
-
if (typeof getter === 'function') {
|
|
499
|
-
return getter.call(headers);
|
|
500
|
-
}
|
|
501
|
-
const single = headers.get('set-cookie');
|
|
502
|
-
return single ? [single] : [];
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function cookieHeaderFromSetCookie(headers: Headers): string | null {
|
|
506
|
-
const cookies = setCookieHeaders(headers)
|
|
507
|
-
.flatMap((header) => header.split(/,(?=\s*[^;,=]+=[^;,]+)/g))
|
|
508
|
-
.map((header) => header.split(';', 1)[0]?.trim() ?? '')
|
|
509
|
-
.filter(Boolean);
|
|
510
|
-
return cookies.length > 0 ? cookies.join('; ') : null;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function resolveVercelProtectionCookie(
|
|
514
|
-
context: RuntimeApiContext,
|
|
515
|
-
): Promise<string | null> {
|
|
516
|
-
const bypassToken = context.vercelProtectionBypassToken?.trim();
|
|
517
|
-
const baseUrl = context.baseUrl?.trim().replace(/\/$/, '');
|
|
518
|
-
if (!bypassToken || !baseUrl) return Promise.resolve(null);
|
|
519
|
-
|
|
520
|
-
const cacheKey = `${baseUrl}\n${bypassToken}`;
|
|
521
|
-
const cached = vercelProtectionCookieCache.get(cacheKey);
|
|
522
|
-
if (cached) return cached;
|
|
523
|
-
|
|
524
|
-
const promise = (async () => {
|
|
525
|
-
const url = new URL(`${baseUrl}/api/v2/health`);
|
|
526
|
-
url.searchParams.set('x-vercel-set-bypass-cookie', 'true');
|
|
527
|
-
url.searchParams.set('x-vercel-protection-bypass', bypassToken);
|
|
528
|
-
const response = await fetch(url.toString(), {
|
|
529
|
-
headers: { 'x-vercel-protection-bypass': bypassToken },
|
|
530
|
-
}).catch(() => null);
|
|
531
|
-
return response ? cookieHeaderFromSetCookie(response.headers) : null;
|
|
532
|
-
})();
|
|
533
|
-
vercelProtectionCookieCache.set(cacheKey, promise);
|
|
534
|
-
return promise;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
487
|
function normalizeRuntimeUserEmail(
|
|
538
488
|
value: string | null | undefined,
|
|
539
489
|
): string | null {
|
|
@@ -546,8 +496,11 @@ async function postRuntimeApi<TResponse>(
|
|
|
546
496
|
body: RuntimeApiActionRequest,
|
|
547
497
|
): Promise<TResponse> {
|
|
548
498
|
const url = resolveRuntimeApiUrl(context);
|
|
549
|
-
const
|
|
550
|
-
|
|
499
|
+
const vercelHeaders = vercelProtectionBypassHeader(
|
|
500
|
+
context.vercelProtectionBypassToken,
|
|
501
|
+
);
|
|
502
|
+
const maxAttempts = runtimeApiMaxAttempts(body.action);
|
|
503
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
551
504
|
let response: Response;
|
|
552
505
|
const abortController = new AbortController();
|
|
553
506
|
const timeout = setTimeout(
|
|
@@ -557,13 +510,13 @@ async function postRuntimeApi<TResponse>(
|
|
|
557
510
|
try {
|
|
558
511
|
response = await fetch(url, {
|
|
559
512
|
method: 'POST',
|
|
560
|
-
headers: resolveRuntimeApiHeaders(context,
|
|
513
|
+
headers: resolveRuntimeApiHeaders(context, vercelHeaders),
|
|
561
514
|
body: JSON.stringify(body),
|
|
562
515
|
signal: abortController.signal,
|
|
563
516
|
});
|
|
564
517
|
} catch (error) {
|
|
565
|
-
if (attempt <
|
|
566
|
-
await sleepRuntimeApiRetry(attempt);
|
|
518
|
+
if (attempt < maxAttempts) {
|
|
519
|
+
await sleepRuntimeApiRetry(body.action, attempt);
|
|
567
520
|
continue;
|
|
568
521
|
}
|
|
569
522
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -605,8 +558,8 @@ async function postRuntimeApi<TResponse>(
|
|
|
605
558
|
isTransientRuntimeApiErrorMessage(
|
|
606
559
|
`${typeof parsed?.error === 'string' ? parsed.error : ''}\n${details ?? ''}`,
|
|
607
560
|
));
|
|
608
|
-
if (shouldRetryRuntimeResponse && attempt <
|
|
609
|
-
await sleepRuntimeApiRetry(attempt, retryAfterMs);
|
|
561
|
+
if (shouldRetryRuntimeResponse && attempt < maxAttempts) {
|
|
562
|
+
await sleepRuntimeApiRetry(body.action, attempt, retryAfterMs);
|
|
610
563
|
continue;
|
|
611
564
|
}
|
|
612
565
|
|
|
@@ -625,11 +578,12 @@ async function postRuntimeApi<TResponse>(
|
|
|
625
578
|
}
|
|
626
579
|
|
|
627
580
|
function runtimeApiRetryDelayMs(
|
|
581
|
+
action: RuntimeApiActionRequest['action'],
|
|
628
582
|
attempt: number,
|
|
629
583
|
retryAfterMs?: number,
|
|
630
584
|
): number {
|
|
631
585
|
const configuredDelay =
|
|
632
|
-
|
|
586
|
+
runtimeApiRetryDelaysForAction(action)[attempt - 1] ??
|
|
633
587
|
RUNTIME_API_DEFAULT_RETRY_AFTER_MS;
|
|
634
588
|
return retryAfterMs === undefined
|
|
635
589
|
? configuredDelay
|
|
@@ -637,14 +591,29 @@ function runtimeApiRetryDelayMs(
|
|
|
637
591
|
}
|
|
638
592
|
|
|
639
593
|
async function sleepRuntimeApiRetry(
|
|
594
|
+
action: RuntimeApiActionRequest['action'],
|
|
640
595
|
attempt: number,
|
|
641
596
|
retryAfterMs?: number,
|
|
642
597
|
): Promise<void> {
|
|
643
598
|
await new Promise((resolve) =>
|
|
644
|
-
setTimeout(resolve, runtimeApiRetryDelayMs(attempt, retryAfterMs)),
|
|
599
|
+
setTimeout(resolve, runtimeApiRetryDelayMs(action, attempt, retryAfterMs)),
|
|
645
600
|
);
|
|
646
601
|
}
|
|
647
602
|
|
|
603
|
+
function runtimeApiRetryDelaysForAction(
|
|
604
|
+
action: RuntimeApiActionRequest['action'],
|
|
605
|
+
): readonly number[] {
|
|
606
|
+
return action === 'resolve_play'
|
|
607
|
+
? RUNTIME_API_RESOLVE_PLAY_RETRY_DELAYS_MS
|
|
608
|
+
: RUNTIME_API_RETRY_DELAYS_MS;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function runtimeApiMaxAttempts(
|
|
612
|
+
action: RuntimeApiActionRequest['action'],
|
|
613
|
+
): number {
|
|
614
|
+
return runtimeApiRetryDelaysForAction(action).length + 1;
|
|
615
|
+
}
|
|
616
|
+
|
|
648
617
|
function isTransientRuntimeApiErrorMessage(message: string): boolean {
|
|
649
618
|
return /timeout exceeded when trying to connect|timed out|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT|requested endpoint could not be found, or you don't have access/i.test(
|
|
650
619
|
message,
|
|
@@ -1303,9 +1272,13 @@ async function prewarmRuntimePostgresSession(
|
|
|
1303
1272
|
attempt += 1
|
|
1304
1273
|
) {
|
|
1305
1274
|
try {
|
|
1306
|
-
await withRuntimePostgres(
|
|
1307
|
-
|
|
1308
|
-
|
|
1275
|
+
await withRuntimePostgres(
|
|
1276
|
+
session,
|
|
1277
|
+
async (client) => {
|
|
1278
|
+
await client.query('SELECT 1');
|
|
1279
|
+
},
|
|
1280
|
+
{ maxConnectAttempts: 1 },
|
|
1281
|
+
);
|
|
1309
1282
|
return;
|
|
1310
1283
|
} catch (error) {
|
|
1311
1284
|
if (
|
|
@@ -1454,17 +1427,17 @@ async function resetRuntimePostgresPool(postgresUrl: string): Promise<void> {
|
|
|
1454
1427
|
async function withRuntimePostgres<T>(
|
|
1455
1428
|
session: RuntimePostgresSession,
|
|
1456
1429
|
fn: (client: RuntimePoolClient) => Promise<T>,
|
|
1457
|
-
options: { cachePool?: boolean } = {},
|
|
1430
|
+
options: { cachePool?: boolean; maxConnectAttempts?: number } = {},
|
|
1458
1431
|
): Promise<T> {
|
|
1459
1432
|
let client: RuntimePoolClient | null = null;
|
|
1460
1433
|
let requestLocalPool: RuntimePool | null = null;
|
|
1461
1434
|
const cachePool =
|
|
1462
1435
|
(options.cachePool ?? true) && canReuseRuntimePostgresPoolsAcrossRequests();
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
) {
|
|
1436
|
+
const maxConnectAttempts =
|
|
1437
|
+
options.maxConnectAttempts === undefined
|
|
1438
|
+
? RUNTIME_POSTGRES_CONNECT_MAX_ATTEMPTS
|
|
1439
|
+
: Math.max(1, Math.floor(options.maxConnectAttempts));
|
|
1440
|
+
for (let attempt = 1; attempt <= maxConnectAttempts; attempt += 1) {
|
|
1468
1441
|
try {
|
|
1469
1442
|
const pool = getPostgresPool(session.postgresUrl, cachePool);
|
|
1470
1443
|
if (!cachePool) {
|
|
@@ -1484,7 +1457,7 @@ async function withRuntimePostgres<T>(
|
|
|
1484
1457
|
requestLocalPool = null;
|
|
1485
1458
|
}
|
|
1486
1459
|
if (
|
|
1487
|
-
attempt >=
|
|
1460
|
+
attempt >= maxConnectAttempts ||
|
|
1488
1461
|
!isTransientRuntimePostgresConnectionError(error)
|
|
1489
1462
|
) {
|
|
1490
1463
|
throw error;
|
|
@@ -2658,6 +2631,8 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
2658
2631
|
'$9::timestamptz',
|
|
2659
2632
|
'$10::integer',
|
|
2660
2633
|
);
|
|
2634
|
+
const targetOlderForeignAttemptOwnerSql =
|
|
2635
|
+
'coalesce(target._attempt_owner_run_id, target._run_id) IS DISTINCT FROM $8::text AND COALESCE(target._attempt_seq, 0) < $10::integer';
|
|
2661
2636
|
const existingAttemptFenceSql = activeRuntimeSheetAttemptFenceSql(
|
|
2662
2637
|
'existing',
|
|
2663
2638
|
'$7::text',
|
|
@@ -2665,6 +2640,8 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
2665
2640
|
'$9::timestamptz',
|
|
2666
2641
|
'$10::integer',
|
|
2667
2642
|
);
|
|
2643
|
+
const existingForeignAttemptOwnerSql =
|
|
2644
|
+
'coalesce(existing._attempt_owner_run_id, existing._run_id) IS DISTINCT FROM $8::text';
|
|
2668
2645
|
const targetNewerTerminalRowSql = newerTerminalRuntimeSheetRowSql(
|
|
2669
2646
|
'target',
|
|
2670
2647
|
'$9::timestamptz',
|
|
@@ -2776,10 +2753,14 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
2776
2753
|
WHERE target._key = input_rows._key
|
|
2777
2754
|
AND target._status IN ('pending', 'running', 'failed')
|
|
2778
2755
|
AND NOT (${targetNewerTerminalRowSql})
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2756
|
+
AND (
|
|
2757
|
+
${targetAttemptFenceSql}
|
|
2758
|
+
OR ${targetOlderForeignAttemptOwnerSql}
|
|
2759
|
+
OR (
|
|
2760
|
+
$11::boolean
|
|
2761
|
+
AND target._status = 'failed'
|
|
2762
|
+
)
|
|
2763
|
+
)
|
|
2783
2764
|
AND (
|
|
2784
2765
|
target._run_id IS DISTINCT FROM $4::text
|
|
2785
2766
|
OR target._input_index IS DISTINCT FROM input_rows._input_index
|
|
@@ -2808,10 +2789,17 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
2808
2789
|
WHERE (
|
|
2809
2790
|
existing._status IN ('pending', 'running', 'failed')
|
|
2810
2791
|
AND NOT (${existingNewerTerminalRowSql})
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2792
|
+
AND (
|
|
2793
|
+
${existingAttemptFenceSql}
|
|
2794
|
+
OR (
|
|
2795
|
+
${existingForeignAttemptOwnerSql}
|
|
2796
|
+
AND COALESCE(existing._attempt_seq, 0) < $10::integer
|
|
2797
|
+
)
|
|
2798
|
+
OR (
|
|
2799
|
+
$11::boolean
|
|
2800
|
+
AND existing._status = 'failed'
|
|
2801
|
+
)
|
|
2802
|
+
)
|
|
2815
2803
|
)
|
|
2816
2804
|
OR (
|
|
2817
2805
|
existing._status = 'enriched'
|
|
@@ -3110,6 +3098,8 @@ async function markRuntimeRowsPendingForRecompute(
|
|
|
3110
3098
|
'$7::timestamptz',
|
|
3111
3099
|
'$8::integer',
|
|
3112
3100
|
);
|
|
3101
|
+
const targetOlderForeignAttemptOwnerSql =
|
|
3102
|
+
'coalesce(target._attempt_owner_run_id, target._run_id) IS DISTINCT FROM $6::text AND COALESCE(target._attempt_seq, 0) < $8::integer';
|
|
3113
3103
|
const targetWritableEnrichedAttemptSql =
|
|
3114
3104
|
writableEnrichedRuntimeSheetAttemptSql(
|
|
3115
3105
|
'target',
|
|
@@ -3138,7 +3128,18 @@ async function markRuntimeRowsPendingForRecompute(
|
|
|
3138
3128
|
FROM target_rows
|
|
3139
3129
|
WHERE target._key = target_rows._key
|
|
3140
3130
|
AND (
|
|
3141
|
-
|
|
3131
|
+
(
|
|
3132
|
+
target._status IN ('pending', 'running', 'failed')
|
|
3133
|
+
AND (
|
|
3134
|
+
${targetAttemptFenceSql}
|
|
3135
|
+
OR ${targetOlderForeignAttemptOwnerSql}
|
|
3136
|
+
OR (
|
|
3137
|
+
$10::boolean
|
|
3138
|
+
AND target._status = 'failed'
|
|
3139
|
+
)
|
|
3140
|
+
)
|
|
3141
|
+
)
|
|
3142
|
+
OR ${targetAttemptFenceSql}
|
|
3142
3143
|
OR (
|
|
3143
3144
|
$10::boolean
|
|
3144
3145
|
AND target._status = 'enriched'
|
|
@@ -3450,6 +3451,10 @@ export async function claimRuntimeWorkReceipt(
|
|
|
3450
3451
|
input.leaseTtlMs,
|
|
3451
3452
|
PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
3452
3453
|
);
|
|
3454
|
+
const claimStatus =
|
|
3455
|
+
input.leaseAware === true
|
|
3456
|
+
? RECEIPT_STATUS_QUEUED
|
|
3457
|
+
: RECEIPT_STATUS_RUNNING;
|
|
3453
3458
|
const claimableStatuses = workReceiptClaimableStatusCodes({
|
|
3454
3459
|
forceRefresh: input.forceRefresh,
|
|
3455
3460
|
forceFailedRefresh: input.forceFailedRefresh,
|
|
@@ -3457,7 +3462,13 @@ export async function claimRuntimeWorkReceipt(
|
|
|
3457
3462
|
});
|
|
3458
3463
|
const { rows } = await client.query<Record<string, unknown>>(
|
|
3459
3464
|
`
|
|
3460
|
-
WITH
|
|
3465
|
+
WITH existing AS MATERIALIZED (
|
|
3466
|
+
SELECT status
|
|
3467
|
+
FROM ${workReceiptTable(session)}
|
|
3468
|
+
WHERE k = decode($1, 'hex')
|
|
3469
|
+
FOR UPDATE
|
|
3470
|
+
),
|
|
3471
|
+
claimed AS (
|
|
3461
3472
|
INSERT INTO ${workReceiptTable(session)} (
|
|
3462
3473
|
k,
|
|
3463
3474
|
status,
|
|
@@ -3500,6 +3511,7 @@ export async function claimRuntimeWorkReceipt(
|
|
|
3500
3511
|
claimableStatusesSql: '$5',
|
|
3501
3512
|
claimantRunIdSql: '$3',
|
|
3502
3513
|
claimantRunAttemptSql: '$9',
|
|
3514
|
+
reclaimRunningSql: '$11',
|
|
3503
3515
|
forceRefreshSql: '$8',
|
|
3504
3516
|
forceFailedRefreshSql: '$10',
|
|
3505
3517
|
})}
|
|
@@ -3525,25 +3537,39 @@ export async function claimRuntimeWorkReceipt(
|
|
|
3525
3537
|
lease_owner_run_id,
|
|
3526
3538
|
lease_owner_attempt,
|
|
3527
3539
|
lease_expires_at,
|
|
3528
|
-
updated_at
|
|
3540
|
+
updated_at,
|
|
3541
|
+
EXISTS (
|
|
3542
|
+
SELECT 1
|
|
3543
|
+
FROM existing
|
|
3544
|
+
WHERE status = $12::smallint
|
|
3545
|
+
) AS refreshed_failed_receipt
|
|
3529
3546
|
FROM claimed
|
|
3530
3547
|
`,
|
|
3531
3548
|
[
|
|
3532
3549
|
workReceiptKeyHex(input.key),
|
|
3533
|
-
|
|
3550
|
+
claimStatus,
|
|
3534
3551
|
input.runId,
|
|
3535
|
-
|
|
3552
|
+
claimStatus,
|
|
3536
3553
|
claimableStatuses,
|
|
3537
3554
|
leaseId,
|
|
3538
3555
|
leaseTtlMs,
|
|
3539
3556
|
input.forceRefresh === true,
|
|
3540
3557
|
runAttempt,
|
|
3541
3558
|
input.forceFailedRefresh === true,
|
|
3559
|
+
input.reclaimRunning === true,
|
|
3560
|
+
RECEIPT_STATUS_CODE.failed,
|
|
3542
3561
|
],
|
|
3543
3562
|
);
|
|
3544
|
-
const
|
|
3563
|
+
const claimedRow = rows[0] ?? null;
|
|
3564
|
+
const claimed = claimedRow ? mapRuntimeWorkReceiptRow(claimedRow) : null;
|
|
3545
3565
|
if (claimed) {
|
|
3546
|
-
return {
|
|
3566
|
+
return {
|
|
3567
|
+
disposition: 'claimed',
|
|
3568
|
+
receipt: claimed,
|
|
3569
|
+
wasFailed:
|
|
3570
|
+
claimedRow.refreshed_failed_receipt === true ||
|
|
3571
|
+
claimedRow.refreshed_failed_receipt === 'true',
|
|
3572
|
+
};
|
|
3547
3573
|
}
|
|
3548
3574
|
|
|
3549
3575
|
const latest = await readRuntimeWorkReceipt(client, session, input.key);
|
|
@@ -3577,7 +3603,11 @@ function runtimeWorkReceiptClaimDispositionForBlockedReceipt(input: {
|
|
|
3577
3603
|
if (receipt.status === 'failed') {
|
|
3578
3604
|
return { disposition: 'failed', receipt };
|
|
3579
3605
|
}
|
|
3580
|
-
if (
|
|
3606
|
+
if (
|
|
3607
|
+
receipt.status === 'queued' ||
|
|
3608
|
+
receipt.status === 'pending' ||
|
|
3609
|
+
receipt.status === 'running'
|
|
3610
|
+
) {
|
|
3581
3611
|
const ownerRunId = receipt.leaseOwnerRunId ?? receipt.runId ?? null;
|
|
3582
3612
|
const leaseExpiresAtMs =
|
|
3583
3613
|
typeof receipt.leaseExpiresAt === 'string'
|
|
@@ -3628,6 +3658,10 @@ export async function claimRuntimeWorkReceipts(
|
|
|
3628
3658
|
const leaseIds = keys.map(() =>
|
|
3629
3659
|
input.leaseAware === true ? newRuntimeWorkReceiptLeaseId() : null,
|
|
3630
3660
|
);
|
|
3661
|
+
const claimStatus =
|
|
3662
|
+
input.leaseAware === true
|
|
3663
|
+
? RECEIPT_STATUS_QUEUED
|
|
3664
|
+
: RECEIPT_STATUS_RUNNING;
|
|
3631
3665
|
const runAttempt = normalizeRuntimeRunAttempt(input.runAttempt);
|
|
3632
3666
|
const leaseTtlMs = normalizeRuntimeLeaseTtlMs(
|
|
3633
3667
|
input.leaseTtlMs,
|
|
@@ -3697,6 +3731,7 @@ export async function claimRuntimeWorkReceipts(
|
|
|
3697
3731
|
claimableStatusesSql: '$6',
|
|
3698
3732
|
claimantRunIdSql: '$4::text',
|
|
3699
3733
|
claimantRunAttemptSql: '$9',
|
|
3734
|
+
reclaimRunningSql: '$11',
|
|
3700
3735
|
forceRefreshSql: '$8',
|
|
3701
3736
|
forceFailedRefreshSql: '$10',
|
|
3702
3737
|
})}
|
|
@@ -3783,14 +3818,15 @@ export async function claimRuntimeWorkReceipts(
|
|
|
3783
3818
|
[
|
|
3784
3819
|
keyHexes,
|
|
3785
3820
|
leaseIds,
|
|
3786
|
-
|
|
3821
|
+
claimStatus,
|
|
3787
3822
|
input.runId,
|
|
3788
|
-
|
|
3823
|
+
claimStatus,
|
|
3789
3824
|
reclaimStatuses,
|
|
3790
3825
|
leaseTtlMs,
|
|
3791
3826
|
input.forceRefresh === true,
|
|
3792
3827
|
runAttempt,
|
|
3793
3828
|
input.forceFailedRefresh === true,
|
|
3829
|
+
input.reclaimRunning === true,
|
|
3794
3830
|
],
|
|
3795
3831
|
);
|
|
3796
3832
|
return rows.map((row) => {
|
|
@@ -3811,6 +3847,317 @@ export async function claimRuntimeWorkReceipts(
|
|
|
3811
3847
|
);
|
|
3812
3848
|
}
|
|
3813
3849
|
|
|
3850
|
+
export async function markRuntimeWorkReceiptRunning(
|
|
3851
|
+
context: RuntimeApiContext,
|
|
3852
|
+
input: {
|
|
3853
|
+
playName: string;
|
|
3854
|
+
runId: string;
|
|
3855
|
+
key: string;
|
|
3856
|
+
runAttempt?: number | null;
|
|
3857
|
+
leaseId?: string | null;
|
|
3858
|
+
leaseTtlMs?: number | null;
|
|
3859
|
+
},
|
|
3860
|
+
): Promise<WorkReceipt | null> {
|
|
3861
|
+
const session = await getRuntimeWorkReceiptSession(context, {
|
|
3862
|
+
playName: input.playName,
|
|
3863
|
+
key: input.key,
|
|
3864
|
+
});
|
|
3865
|
+
return await withRuntimeWorkReceiptClient(
|
|
3866
|
+
context,
|
|
3867
|
+
session,
|
|
3868
|
+
async (client) => {
|
|
3869
|
+
const leaseId = input.leaseId?.trim() || null;
|
|
3870
|
+
const runAttempt = normalizeRuntimeRunAttempt(input.runAttempt);
|
|
3871
|
+
const leaseTtlMs = normalizeRuntimeLeaseTtlMs(
|
|
3872
|
+
input.leaseTtlMs,
|
|
3873
|
+
PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
3874
|
+
);
|
|
3875
|
+
const { rows } = await client.query<Record<string, unknown>>(
|
|
3876
|
+
`
|
|
3877
|
+
WITH running AS (
|
|
3878
|
+
UPDATE ${workReceiptTable(session)}
|
|
3879
|
+
SET status = $2::smallint,
|
|
3880
|
+
lease_expires_at = CASE
|
|
3881
|
+
WHEN lease_id IS NULL THEN NULL
|
|
3882
|
+
ELSE now() + ($6::double precision * interval '1 millisecond')
|
|
3883
|
+
END,
|
|
3884
|
+
updated_at = now()
|
|
3885
|
+
WHERE k = decode($1, 'hex')
|
|
3886
|
+
AND ${workReceiptActiveOwnerPredicateSql({
|
|
3887
|
+
receiptTable: workReceiptTable(session),
|
|
3888
|
+
ownerRunIdSql: '$3',
|
|
3889
|
+
ownerRunAttemptSql: '$5',
|
|
3890
|
+
leaseIdSql: '$4::text',
|
|
3891
|
+
})}
|
|
3892
|
+
RETURNING convert_from(k, 'UTF8') AS k,
|
|
3893
|
+
status,
|
|
3894
|
+
output,
|
|
3895
|
+
error,
|
|
3896
|
+
failure_kind,
|
|
3897
|
+
run_id,
|
|
3898
|
+
lease_id,
|
|
3899
|
+
lease_owner_run_id,
|
|
3900
|
+
lease_owner_attempt,
|
|
3901
|
+
lease_expires_at,
|
|
3902
|
+
updated_at
|
|
3903
|
+
)
|
|
3904
|
+
SELECT k, status, output, error, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at, updated_at
|
|
3905
|
+
FROM running
|
|
3906
|
+
`,
|
|
3907
|
+
[
|
|
3908
|
+
workReceiptKeyHex(input.key),
|
|
3909
|
+
RECEIPT_STATUS_RUNNING,
|
|
3910
|
+
input.runId,
|
|
3911
|
+
leaseId,
|
|
3912
|
+
runAttempt,
|
|
3913
|
+
leaseTtlMs,
|
|
3914
|
+
],
|
|
3915
|
+
);
|
|
3916
|
+
return rows[0] ? mapRuntimeWorkReceiptRow(rows[0]) : null;
|
|
3917
|
+
},
|
|
3918
|
+
);
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3921
|
+
export async function markRuntimeWorkReceiptsRunning(
|
|
3922
|
+
context: RuntimeApiContext,
|
|
3923
|
+
input: {
|
|
3924
|
+
playName: string;
|
|
3925
|
+
receipts: Array<{
|
|
3926
|
+
runId: string;
|
|
3927
|
+
key: string;
|
|
3928
|
+
leaseId?: string | null;
|
|
3929
|
+
runAttempt?: number | null;
|
|
3930
|
+
}>;
|
|
3931
|
+
leaseTtlMs?: number | null;
|
|
3932
|
+
},
|
|
3933
|
+
): Promise<Array<WorkReceipt | null>> {
|
|
3934
|
+
const receipts = input.receipts.filter((receipt) => receipt.key.trim());
|
|
3935
|
+
if (receipts.length === 0) return [];
|
|
3936
|
+
const leaseTtlMs = normalizeRuntimeLeaseTtlMs(
|
|
3937
|
+
input.leaseTtlMs,
|
|
3938
|
+
PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
3939
|
+
);
|
|
3940
|
+
const session = await getRuntimeWorkReceiptSessionForKeys(context, {
|
|
3941
|
+
playName: input.playName,
|
|
3942
|
+
keys: receipts.map((receipt) => receipt.key),
|
|
3943
|
+
});
|
|
3944
|
+
return await withRuntimeWorkReceiptClient(
|
|
3945
|
+
context,
|
|
3946
|
+
session,
|
|
3947
|
+
async (client) => {
|
|
3948
|
+
const { rows } = await client.query<Record<string, unknown>>(
|
|
3949
|
+
`
|
|
3950
|
+
WITH inputs AS (
|
|
3951
|
+
SELECT key_values.key_hex,
|
|
3952
|
+
run_values.run_id,
|
|
3953
|
+
lease_values.lease_id,
|
|
3954
|
+
attempt_values.run_attempt,
|
|
3955
|
+
key_values.ord
|
|
3956
|
+
FROM unnest($1::text[]) WITH ORDINALITY AS key_values(key_hex, ord)
|
|
3957
|
+
JOIN unnest($2::text[]) WITH ORDINALITY AS run_values(run_id, ord)
|
|
3958
|
+
ON run_values.ord = key_values.ord
|
|
3959
|
+
JOIN unnest($3::text[]) WITH ORDINALITY AS lease_values(lease_id, ord)
|
|
3960
|
+
ON lease_values.ord = key_values.ord
|
|
3961
|
+
JOIN unnest($4::integer[]) WITH ORDINALITY AS attempt_values(run_attempt, ord)
|
|
3962
|
+
ON attempt_values.ord = key_values.ord
|
|
3963
|
+
),
|
|
3964
|
+
running AS (
|
|
3965
|
+
UPDATE ${workReceiptTable(session)} AS target
|
|
3966
|
+
SET status = $5::smallint,
|
|
3967
|
+
lease_expires_at = CASE
|
|
3968
|
+
WHEN target.lease_id IS NULL THEN NULL
|
|
3969
|
+
ELSE now() + ($6::double precision * interval '1 millisecond')
|
|
3970
|
+
END,
|
|
3971
|
+
updated_at = now()
|
|
3972
|
+
FROM inputs
|
|
3973
|
+
WHERE target.k = decode(inputs.key_hex, 'hex')
|
|
3974
|
+
AND ${workReceiptActiveOwnerPredicateSql({
|
|
3975
|
+
receiptTable: 'target',
|
|
3976
|
+
ownerRunIdSql: 'inputs.run_id',
|
|
3977
|
+
ownerRunAttemptSql: 'inputs.run_attempt',
|
|
3978
|
+
leaseIdSql: 'inputs.lease_id',
|
|
3979
|
+
})}
|
|
3980
|
+
RETURNING target.k,
|
|
3981
|
+
target.status,
|
|
3982
|
+
target.output,
|
|
3983
|
+
target.error,
|
|
3984
|
+
target.failure_kind,
|
|
3985
|
+
target.run_id,
|
|
3986
|
+
target.lease_id,
|
|
3987
|
+
target.lease_owner_run_id,
|
|
3988
|
+
target.lease_owner_attempt,
|
|
3989
|
+
target.lease_expires_at,
|
|
3990
|
+
target.updated_at,
|
|
3991
|
+
inputs.ord
|
|
3992
|
+
),
|
|
3993
|
+
returned AS (
|
|
3994
|
+
SELECT running.k,
|
|
3995
|
+
running.status,
|
|
3996
|
+
running.output,
|
|
3997
|
+
running.error,
|
|
3998
|
+
running.failure_kind,
|
|
3999
|
+
running.run_id,
|
|
4000
|
+
running.lease_id,
|
|
4001
|
+
running.lease_owner_run_id,
|
|
4002
|
+
running.lease_owner_attempt,
|
|
4003
|
+
running.lease_expires_at,
|
|
4004
|
+
running.updated_at,
|
|
4005
|
+
inputs.ord
|
|
4006
|
+
FROM inputs
|
|
4007
|
+
LEFT JOIN running ON running.ord = inputs.ord
|
|
4008
|
+
)
|
|
4009
|
+
SELECT convert_from(returned.k, 'UTF8') AS k,
|
|
4010
|
+
returned.status,
|
|
4011
|
+
returned.output,
|
|
4012
|
+
returned.error,
|
|
4013
|
+
returned.failure_kind,
|
|
4014
|
+
returned.run_id,
|
|
4015
|
+
returned.lease_id,
|
|
4016
|
+
returned.lease_owner_run_id,
|
|
4017
|
+
returned.lease_owner_attempt,
|
|
4018
|
+
returned.lease_expires_at,
|
|
4019
|
+
returned.updated_at
|
|
4020
|
+
FROM returned
|
|
4021
|
+
ORDER BY returned.ord
|
|
4022
|
+
`,
|
|
4023
|
+
[
|
|
4024
|
+
receipts.map((receipt) => workReceiptKeyHex(receipt.key)),
|
|
4025
|
+
receipts.map((receipt) => receipt.runId),
|
|
4026
|
+
receipts.map((receipt) => receipt.leaseId?.trim() || null),
|
|
4027
|
+
receipts.map((receipt) =>
|
|
4028
|
+
normalizeRuntimeRunAttempt(receipt.runAttempt),
|
|
4029
|
+
),
|
|
4030
|
+
RECEIPT_STATUS_RUNNING,
|
|
4031
|
+
leaseTtlMs,
|
|
4032
|
+
],
|
|
4033
|
+
);
|
|
4034
|
+
return rows.map((row) =>
|
|
4035
|
+
row.k == null ? null : mapRuntimeWorkReceiptRow(row),
|
|
4036
|
+
);
|
|
4037
|
+
},
|
|
4038
|
+
);
|
|
4039
|
+
}
|
|
4040
|
+
|
|
4041
|
+
export async function markRuntimeWorkReceiptsQueued(
|
|
4042
|
+
context: RuntimeApiContext,
|
|
4043
|
+
input: {
|
|
4044
|
+
playName: string;
|
|
4045
|
+
receipts: Array<{
|
|
4046
|
+
runId: string;
|
|
4047
|
+
key: string;
|
|
4048
|
+
leaseId?: string | null;
|
|
4049
|
+
runAttempt?: number | null;
|
|
4050
|
+
}>;
|
|
4051
|
+
leaseTtlMs?: number | null;
|
|
4052
|
+
},
|
|
4053
|
+
): Promise<Array<WorkReceipt | null>> {
|
|
4054
|
+
const receipts = input.receipts.filter((receipt) => receipt.key.trim());
|
|
4055
|
+
if (receipts.length === 0) return [];
|
|
4056
|
+
const leaseTtlMs = normalizeRuntimeLeaseTtlMs(
|
|
4057
|
+
input.leaseTtlMs,
|
|
4058
|
+
PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
4059
|
+
);
|
|
4060
|
+
const session = await getRuntimeWorkReceiptSessionForKeys(context, {
|
|
4061
|
+
playName: input.playName,
|
|
4062
|
+
keys: receipts.map((receipt) => receipt.key),
|
|
4063
|
+
});
|
|
4064
|
+
return await withRuntimeWorkReceiptClient(
|
|
4065
|
+
context,
|
|
4066
|
+
session,
|
|
4067
|
+
async (client) => {
|
|
4068
|
+
const { rows } = await client.query<Record<string, unknown>>(
|
|
4069
|
+
`
|
|
4070
|
+
WITH inputs AS (
|
|
4071
|
+
SELECT key_values.key_hex,
|
|
4072
|
+
run_values.run_id,
|
|
4073
|
+
lease_values.lease_id,
|
|
4074
|
+
attempt_values.run_attempt,
|
|
4075
|
+
key_values.ord
|
|
4076
|
+
FROM unnest($1::text[]) WITH ORDINALITY AS key_values(key_hex, ord)
|
|
4077
|
+
JOIN unnest($2::text[]) WITH ORDINALITY AS run_values(run_id, ord)
|
|
4078
|
+
ON run_values.ord = key_values.ord
|
|
4079
|
+
JOIN unnest($3::text[]) WITH ORDINALITY AS lease_values(lease_id, ord)
|
|
4080
|
+
ON lease_values.ord = key_values.ord
|
|
4081
|
+
JOIN unnest($4::integer[]) WITH ORDINALITY AS attempt_values(run_attempt, ord)
|
|
4082
|
+
ON attempt_values.ord = key_values.ord
|
|
4083
|
+
),
|
|
4084
|
+
queued AS (
|
|
4085
|
+
UPDATE ${workReceiptTable(session)} AS target
|
|
4086
|
+
SET status = $5::smallint,
|
|
4087
|
+
lease_expires_at = CASE
|
|
4088
|
+
WHEN target.lease_id IS NULL THEN NULL
|
|
4089
|
+
ELSE now() + ($6::double precision * interval '1 millisecond')
|
|
4090
|
+
END,
|
|
4091
|
+
updated_at = now()
|
|
4092
|
+
FROM inputs
|
|
4093
|
+
WHERE target.k = decode(inputs.key_hex, 'hex')
|
|
4094
|
+
AND ${workReceiptActiveOwnerPredicateSql({
|
|
4095
|
+
receiptTable: 'target',
|
|
4096
|
+
ownerRunIdSql: 'inputs.run_id',
|
|
4097
|
+
ownerRunAttemptSql: 'inputs.run_attempt',
|
|
4098
|
+
leaseIdSql: 'inputs.lease_id',
|
|
4099
|
+
})}
|
|
4100
|
+
RETURNING target.k,
|
|
4101
|
+
target.status,
|
|
4102
|
+
target.output,
|
|
4103
|
+
target.error,
|
|
4104
|
+
target.failure_kind,
|
|
4105
|
+
target.run_id,
|
|
4106
|
+
target.lease_id,
|
|
4107
|
+
target.lease_owner_run_id,
|
|
4108
|
+
target.lease_owner_attempt,
|
|
4109
|
+
target.lease_expires_at,
|
|
4110
|
+
target.updated_at,
|
|
4111
|
+
inputs.ord
|
|
4112
|
+
),
|
|
4113
|
+
returned AS (
|
|
4114
|
+
SELECT queued.k,
|
|
4115
|
+
queued.status,
|
|
4116
|
+
queued.output,
|
|
4117
|
+
queued.error,
|
|
4118
|
+
queued.failure_kind,
|
|
4119
|
+
queued.run_id,
|
|
4120
|
+
queued.lease_id,
|
|
4121
|
+
queued.lease_owner_run_id,
|
|
4122
|
+
queued.lease_owner_attempt,
|
|
4123
|
+
queued.lease_expires_at,
|
|
4124
|
+
queued.updated_at,
|
|
4125
|
+
inputs.ord
|
|
4126
|
+
FROM inputs
|
|
4127
|
+
LEFT JOIN queued ON queued.ord = inputs.ord
|
|
4128
|
+
)
|
|
4129
|
+
SELECT convert_from(returned.k, 'UTF8') AS k,
|
|
4130
|
+
returned.status,
|
|
4131
|
+
returned.output,
|
|
4132
|
+
returned.error,
|
|
4133
|
+
returned.failure_kind,
|
|
4134
|
+
returned.run_id,
|
|
4135
|
+
returned.lease_id,
|
|
4136
|
+
returned.lease_owner_run_id,
|
|
4137
|
+
returned.lease_owner_attempt,
|
|
4138
|
+
returned.lease_expires_at,
|
|
4139
|
+
returned.updated_at
|
|
4140
|
+
FROM returned
|
|
4141
|
+
ORDER BY returned.ord
|
|
4142
|
+
`,
|
|
4143
|
+
[
|
|
4144
|
+
receipts.map((receipt) => workReceiptKeyHex(receipt.key)),
|
|
4145
|
+
receipts.map((receipt) => receipt.runId),
|
|
4146
|
+
receipts.map((receipt) => receipt.leaseId?.trim() || null),
|
|
4147
|
+
receipts.map((receipt) =>
|
|
4148
|
+
normalizeRuntimeRunAttempt(receipt.runAttempt),
|
|
4149
|
+
),
|
|
4150
|
+
RECEIPT_STATUS_QUEUED,
|
|
4151
|
+
leaseTtlMs,
|
|
4152
|
+
],
|
|
4153
|
+
);
|
|
4154
|
+
return rows.map((row) =>
|
|
4155
|
+
row.k == null ? null : mapRuntimeWorkReceiptRow(row),
|
|
4156
|
+
);
|
|
4157
|
+
},
|
|
4158
|
+
);
|
|
4159
|
+
}
|
|
4160
|
+
|
|
3814
4161
|
export async function completeRuntimeWorkReceipt(
|
|
3815
4162
|
context: RuntimeApiContext,
|
|
3816
4163
|
input: {
|
|
@@ -3876,7 +4223,7 @@ export async function completeRuntimeWorkReceipt(
|
|
|
3876
4223
|
lease_expires_at = NULL,
|
|
3877
4224
|
updated_at = now()
|
|
3878
4225
|
WHERE k = decode($1, 'hex')
|
|
3879
|
-
AND ${
|
|
4226
|
+
AND ${workReceiptActiveOwnerPredicateSql({
|
|
3880
4227
|
receiptTable: workReceiptTable(session),
|
|
3881
4228
|
ownerRunIdSql: '$4',
|
|
3882
4229
|
ownerRunAttemptSql: '$6',
|
|
@@ -4012,7 +4359,7 @@ export async function completeRuntimeWorkReceipts(
|
|
|
4012
4359
|
updated_at = now()
|
|
4013
4360
|
FROM inputs
|
|
4014
4361
|
WHERE target.k = decode(inputs.key_hex, 'hex')
|
|
4015
|
-
AND ${
|
|
4362
|
+
AND ${workReceiptActiveOwnerPredicateSql({
|
|
4016
4363
|
receiptTable: 'target',
|
|
4017
4364
|
ownerRunIdSql: 'inputs.run_id',
|
|
4018
4365
|
ownerRunAttemptSql: 'inputs.run_attempt',
|
|
@@ -4120,7 +4467,7 @@ export async function failRuntimeWorkReceipt(
|
|
|
4120
4467
|
lease_expires_at = NULL,
|
|
4121
4468
|
updated_at = now()
|
|
4122
4469
|
WHERE k = decode($1, 'hex')
|
|
4123
|
-
AND ${
|
|
4470
|
+
AND ${workReceiptActiveOwnerPredicateSql({
|
|
4124
4471
|
receiptTable: workReceiptTable(session),
|
|
4125
4472
|
ownerRunIdSql: '$4',
|
|
4126
4473
|
ownerRunAttemptSql: '$7',
|
|
@@ -4190,7 +4537,7 @@ export async function releaseRuntimeWorkReceipt(
|
|
|
4190
4537
|
lease_expires_at = NULL,
|
|
4191
4538
|
updated_at = now()
|
|
4192
4539
|
WHERE k = decode($1, 'hex')
|
|
4193
|
-
AND ${
|
|
4540
|
+
AND ${workReceiptActiveOwnerPredicateSql({
|
|
4194
4541
|
receiptTable: workReceiptTable(session),
|
|
4195
4542
|
ownerRunIdSql: '$3',
|
|
4196
4543
|
ownerRunAttemptSql: '$5',
|
|
@@ -4283,7 +4630,7 @@ export async function failRuntimeWorkReceipts(
|
|
|
4283
4630
|
updated_at = now()
|
|
4284
4631
|
FROM inputs
|
|
4285
4632
|
WHERE target.k = decode(inputs.key_hex, 'hex')
|
|
4286
|
-
AND ${
|
|
4633
|
+
AND ${workReceiptActiveOwnerPredicateSql({
|
|
4287
4634
|
receiptTable: 'target',
|
|
4288
4635
|
ownerRunIdSql: 'inputs.run_id',
|
|
4289
4636
|
ownerRunAttemptSql: 'inputs.run_attempt',
|
|
@@ -4493,7 +4840,7 @@ export async function skipRuntimeWorkReceipt(
|
|
|
4493
4840
|
lease_expires_at = NULL,
|
|
4494
4841
|
updated_at = now()
|
|
4495
4842
|
WHERE k = decode($1, 'hex')
|
|
4496
|
-
AND ${
|
|
4843
|
+
AND ${workReceiptActiveOwnerPredicateSql({
|
|
4497
4844
|
receiptTable: workReceiptTable(session),
|
|
4498
4845
|
ownerRunIdSql: '$4',
|
|
4499
4846
|
ownerRunAttemptSql: '$6',
|
|
@@ -5122,6 +5469,13 @@ async function completeRuntimeMapRowChunks(
|
|
|
5122
5469
|
'$11::timestamptz',
|
|
5123
5470
|
'$12',
|
|
5124
5471
|
);
|
|
5472
|
+
const writableNonTerminalAttemptSql = `(
|
|
5473
|
+
target._status <> 'enriched'
|
|
5474
|
+
AND (
|
|
5475
|
+
${activeRuntimeSheetAttemptFenceSql('target', '$9::text', '$10::text', '$11::timestamptz', '$12::integer')}
|
|
5476
|
+
OR coalesce(target._attempt_owner_run_id, target._run_id) IS DISTINCT FROM $10::text
|
|
5477
|
+
)
|
|
5478
|
+
)`;
|
|
5125
5479
|
const sameOwnerTerminalEpochSql = sameOwnerTerminalAttemptEpochSql(
|
|
5126
5480
|
'target',
|
|
5127
5481
|
'$10::text',
|
|
@@ -5174,6 +5528,7 @@ async function completeRuntimeMapRowChunks(
|
|
|
5174
5528
|
AND NOT (${sameOwnerTerminalEpochSql})
|
|
5175
5529
|
AND (
|
|
5176
5530
|
($9::text IS NULL AND target._run_id = $5::text)
|
|
5531
|
+
OR ($9::text IS NOT NULL AND ${writableNonTerminalAttemptSql})
|
|
5177
5532
|
OR ($9::text IS NOT NULL AND ${activeRuntimeSheetAttemptFenceSql('target', '$9::text', '$10::text', '$11::timestamptz', '$12::integer')})
|
|
5178
5533
|
OR ($9::text IS NOT NULL AND ${writableEnrichedAttemptSql})
|
|
5179
5534
|
)
|
|
@@ -5208,6 +5563,7 @@ async function completeRuntimeMapRowChunks(
|
|
|
5208
5563
|
(${newerTerminalRuntimeSheetRowSql('target', '$11::timestamptz', '$12::integer', '$9::text', '$10::text')})
|
|
5209
5564
|
OR NOT (
|
|
5210
5565
|
($9::text IS NULL AND target._run_id = $5::text)
|
|
5566
|
+
OR ($9::text IS NOT NULL AND ${writableNonTerminalAttemptSql})
|
|
5211
5567
|
OR ($9::text IS NOT NULL AND ${activeRuntimeSheetAttemptFenceSql('target', '$9::text', '$10::text', '$11::timestamptz', '$12::integer')})
|
|
5212
5568
|
OR ($9::text IS NOT NULL AND ${writableEnrichedAttemptSql})
|
|
5213
5569
|
)
|