deepline 0.3.183 → 0.3.185
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/app-runtime-api.ts +53 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +751 -193
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +3 -1
- package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +437 -150
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +47 -4
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +3 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +18 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +22 -2
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +11 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +102 -12
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +8 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +136 -28
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-scope.ts +80 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +897 -207
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +33 -1
- package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +120 -34
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +82 -2
- package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +7 -2
- package/dist/cli/index.js +28 -3
- package/dist/cli/index.mjs +28 -3
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/install-integrity.json +1 -0
- 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
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts
CHANGED
|
@@ -1288,10 +1288,14 @@ export async function parkAndResolveSuspension(input: {
|
|
|
1288
1288
|
boundaryId: suspension.boundaryId,
|
|
1289
1289
|
eventKey: suspension.eventKey,
|
|
1290
1290
|
timeoutMs: suspension.timeoutMs,
|
|
1291
|
+
...(suspension.eventExpiresAtMs !== undefined
|
|
1292
|
+
? { eventExpiresAtMs: suspension.eventExpiresAtMs }
|
|
1293
|
+
: {}),
|
|
1294
|
+
...(suspension.postTimeoutPark ? { postTimeoutPark: true } : {}),
|
|
1291
1295
|
...(suspension.messageRef ? { messageRef: suspension.messageRef } : {}),
|
|
1292
1296
|
},
|
|
1293
1297
|
claimSignal: input.claimSignal,
|
|
1294
|
-
});
|
|
1298
|
+
}).then((result) => result.checkpoint);
|
|
1295
1299
|
}
|
|
1296
1300
|
|
|
1297
1301
|
if (suspension.kind === 'integration_event_batch') {
|
|
@@ -1299,13 +1303,25 @@ export async function parkAndResolveSuspension(input: {
|
|
|
1299
1303
|
for (const boundary of [...suspension.boundaries].sort(
|
|
1300
1304
|
(a, b) => a.timeoutMs - b.timeoutMs,
|
|
1301
1305
|
)) {
|
|
1302
|
-
|
|
1306
|
+
// An earlier event may have resolved while the provider timer elapsed.
|
|
1307
|
+
// Do not enter another event wait after that provider row is due.
|
|
1308
|
+
if (
|
|
1309
|
+
suspension.providerWakeAtMs !== undefined &&
|
|
1310
|
+
suspension.providerWakeAtMs <= Date.now() &&
|
|
1311
|
+
boundary.eventExpiresAtMs !== undefined &&
|
|
1312
|
+
boundary.eventExpiresAtMs > Date.now()
|
|
1313
|
+
)
|
|
1314
|
+
return resolvedCheckpoint;
|
|
1315
|
+
const resolved = await resolveIntegrationEvent({
|
|
1303
1316
|
ctx,
|
|
1304
1317
|
runId,
|
|
1305
1318
|
checkpoint: resolvedCheckpoint,
|
|
1306
1319
|
boundary,
|
|
1307
1320
|
claimSignal: input.claimSignal,
|
|
1321
|
+
providerWakeAtMs: suspension.providerWakeAtMs,
|
|
1308
1322
|
});
|
|
1323
|
+
resolvedCheckpoint = resolved.checkpoint;
|
|
1324
|
+
if (resolved.providerWake) return resolvedCheckpoint;
|
|
1309
1325
|
}
|
|
1310
1326
|
return resolvedCheckpoint;
|
|
1311
1327
|
}
|
|
@@ -1382,13 +1398,16 @@ async function resolveIntegrationEvent(input: {
|
|
|
1382
1398
|
boundaryId: string;
|
|
1383
1399
|
eventKey: string;
|
|
1384
1400
|
timeoutMs: number;
|
|
1401
|
+
eventExpiresAtMs?: number;
|
|
1402
|
+
postTimeoutPark?: boolean;
|
|
1385
1403
|
messageRef?: { channel: string; ts: string };
|
|
1386
1404
|
};
|
|
1405
|
+
providerWakeAtMs?: number;
|
|
1387
1406
|
claimSignal: (
|
|
1388
1407
|
eventKey: string,
|
|
1389
1408
|
boundaryId: string | null,
|
|
1390
1409
|
) => Promise<PostgresSchedulerSignalRecord | null>;
|
|
1391
|
-
}): Promise<PlayCheckpoint> {
|
|
1410
|
+
}): Promise<{ checkpoint: PlayCheckpoint; providerWake: boolean }> {
|
|
1392
1411
|
const { ctx, runId, checkpoint, boundary } = input;
|
|
1393
1412
|
// Suspensions written before `messageRef` became part of the suspension
|
|
1394
1413
|
// contract still have the armed Slack message reference in their checkpoint.
|
|
@@ -1403,31 +1422,89 @@ async function resolveIntegrationEvent(input: {
|
|
|
1403
1422
|
(checkpointBoundary?.kind === 'integration_event'
|
|
1404
1423
|
? checkpointBoundary.messageRef
|
|
1405
1424
|
: undefined);
|
|
1425
|
+
const checkpointOutput =
|
|
1426
|
+
checkpointBoundary?.kind === 'integration_event'
|
|
1427
|
+
? checkpointBoundary.output
|
|
1428
|
+
: null;
|
|
1429
|
+
const postTimeoutPark =
|
|
1430
|
+
boundary.postTimeoutPark === true ||
|
|
1431
|
+
(checkpointOutput !== null &&
|
|
1432
|
+
typeof checkpointOutput === 'object' &&
|
|
1433
|
+
!Array.isArray(checkpointOutput) &&
|
|
1434
|
+
(checkpointOutput as Record<string, unknown>).timed_out === true);
|
|
1435
|
+
const originalEventExpiresAtMs =
|
|
1436
|
+
boundary.eventExpiresAtMs ??
|
|
1437
|
+
(checkpointBoundary?.kind === 'integration_event'
|
|
1438
|
+
? checkpointBoundary.eventExpiresAtMs
|
|
1439
|
+
: undefined);
|
|
1440
|
+
const terminalParkAnchorMs =
|
|
1441
|
+
checkpointBoundary?.kind === 'integration_event'
|
|
1442
|
+
? (checkpointBoundary.completedAt ?? Date.now())
|
|
1443
|
+
: Date.now();
|
|
1444
|
+
const eventExpiresAtMs = postTimeoutPark
|
|
1445
|
+
? (boundary.eventExpiresAtMs ??
|
|
1446
|
+
terminalParkAnchorMs + Math.max(0, boundary.timeoutMs))
|
|
1447
|
+
: (originalEventExpiresAtMs ??
|
|
1448
|
+
Date.now() + Math.max(0, boundary.timeoutMs));
|
|
1449
|
+
// Old event checkpoints have only an Absurd step timer, not an absolute
|
|
1450
|
+
// expiry. Reuse that original step instead of granting a new full timeout
|
|
1451
|
+
// whenever a provider row wakes.
|
|
1452
|
+
const providerWakeAtMs =
|
|
1453
|
+
postTimeoutPark || originalEventExpiresAtMs === undefined
|
|
1454
|
+
? undefined
|
|
1455
|
+
: input.providerWakeAtMs;
|
|
1406
1456
|
|
|
1407
1457
|
// Belt-and-suspenders: an early durable signal short-circuits the wait.
|
|
1408
|
-
const early =
|
|
1458
|
+
const early = postTimeoutPark
|
|
1459
|
+
? null
|
|
1460
|
+
: await input.claimSignal(boundary.eventKey, boundary.boundaryId);
|
|
1409
1461
|
if (early) {
|
|
1410
|
-
return
|
|
1411
|
-
checkpoint
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
timeoutMs: boundary.timeoutMs,
|
|
1415
|
-
...(messageRef ? { messageRef } : {}),
|
|
1416
|
-
output: absurdIntegrationEventBoundaryOutput({
|
|
1462
|
+
return {
|
|
1463
|
+
checkpoint: absurdCheckpointWithIntegrationEvent({
|
|
1464
|
+
checkpoint,
|
|
1465
|
+
boundaryId: boundary.boundaryId,
|
|
1417
1466
|
eventKey: boundary.eventKey,
|
|
1418
|
-
|
|
1419
|
-
messageRef,
|
|
1420
|
-
|
|
1467
|
+
timeoutMs: boundary.timeoutMs,
|
|
1468
|
+
...(messageRef ? { messageRef } : {}),
|
|
1469
|
+
output: absurdIntegrationEventBoundaryOutput({
|
|
1470
|
+
eventKey: boundary.eventKey,
|
|
1471
|
+
signalData: signalData(early),
|
|
1472
|
+
messageRef,
|
|
1473
|
+
timedOut: false,
|
|
1474
|
+
}),
|
|
1421
1475
|
}),
|
|
1422
|
-
|
|
1476
|
+
providerWake: false,
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
if (
|
|
1481
|
+
providerWakeAtMs !== undefined &&
|
|
1482
|
+
providerWakeAtMs <= Date.now() &&
|
|
1483
|
+
eventExpiresAtMs > Date.now()
|
|
1484
|
+
) {
|
|
1485
|
+
return { checkpoint: absurdCheckpointBase(checkpoint), providerWake: true };
|
|
1423
1486
|
}
|
|
1424
1487
|
|
|
1425
1488
|
const eventName = absurdIntegrationEventName(runId, boundary.eventKey);
|
|
1489
|
+
const eventWaitMs = Math.max(0, eventExpiresAtMs - Date.now());
|
|
1490
|
+
const providerWaitMs =
|
|
1491
|
+
providerWakeAtMs === undefined
|
|
1492
|
+
? Number.POSITIVE_INFINITY
|
|
1493
|
+
: Math.max(0, providerWakeAtMs - Date.now());
|
|
1494
|
+
const waitMs = Math.min(eventWaitMs, providerWaitMs);
|
|
1426
1495
|
let timedOut = false;
|
|
1427
1496
|
try {
|
|
1428
1497
|
await ctx.awaitEvent(eventName, {
|
|
1429
|
-
|
|
1430
|
-
timeout
|
|
1498
|
+
// Absurd persists waits by step name. A provider timer must never
|
|
1499
|
+
// inherit an earlier event timeout (or vice versa) on replay.
|
|
1500
|
+
stepName: postTimeoutPark
|
|
1501
|
+
? `${boundary.boundaryId}:terminal-park:${terminalParkAnchorMs}`
|
|
1502
|
+
: providerWakeAtMs === undefined
|
|
1503
|
+
? boundary.boundaryId
|
|
1504
|
+
: `${boundary.boundaryId}:provider-wake:${providerWakeAtMs}`,
|
|
1505
|
+
// Absurd floors seconds. Round up so a positive remainder never wakes
|
|
1506
|
+
// before the provider's authoritative not-before time.
|
|
1507
|
+
timeout: Math.ceil(waitMs / 1_000),
|
|
1431
1508
|
});
|
|
1432
1509
|
} catch (error) {
|
|
1433
1510
|
if (error instanceof TimeoutError) {
|
|
@@ -1438,6 +1515,25 @@ async function resolveIntegrationEvent(input: {
|
|
|
1438
1515
|
}
|
|
1439
1516
|
}
|
|
1440
1517
|
|
|
1518
|
+
if (postTimeoutPark) {
|
|
1519
|
+
return {
|
|
1520
|
+
checkpoint: absurdCheckpointWithIntegrationEvent({
|
|
1521
|
+
checkpoint,
|
|
1522
|
+
boundaryId: boundary.boundaryId,
|
|
1523
|
+
eventKey: boundary.eventKey,
|
|
1524
|
+
timeoutMs: boundary.timeoutMs,
|
|
1525
|
+
...(messageRef ? { messageRef } : {}),
|
|
1526
|
+
output:
|
|
1527
|
+
checkpointOutput !== null &&
|
|
1528
|
+
typeof checkpointOutput === 'object' &&
|
|
1529
|
+
!Array.isArray(checkpointOutput)
|
|
1530
|
+
? (checkpointOutput as Record<string, unknown>)
|
|
1531
|
+
: { timed_out: true },
|
|
1532
|
+
}),
|
|
1533
|
+
providerWake: false,
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1441
1537
|
// Claim the authoritative durable signal row for the payload — the absurd
|
|
1442
1538
|
// event carries no payload we trust. On timeout, sweep once more before
|
|
1443
1539
|
// giving up (mirrors today's belt-and-suspenders resume).
|
|
@@ -1445,20 +1541,32 @@ async function resolveIntegrationEvent(input: {
|
|
|
1445
1541
|
boundary.eventKey,
|
|
1446
1542
|
boundary.boundaryId,
|
|
1447
1543
|
);
|
|
1544
|
+
if (
|
|
1545
|
+
!claimed &&
|
|
1546
|
+
timedOut &&
|
|
1547
|
+
providerWakeAtMs !== undefined &&
|
|
1548
|
+
providerWakeAtMs < eventExpiresAtMs &&
|
|
1549
|
+
Date.now() < eventExpiresAtMs
|
|
1550
|
+
) {
|
|
1551
|
+
return { checkpoint: absurdCheckpointBase(checkpoint), providerWake: true };
|
|
1552
|
+
}
|
|
1448
1553
|
const resolvedTimedOut = timedOut && !claimed;
|
|
1449
|
-
return
|
|
1450
|
-
checkpoint
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
timeoutMs: boundary.timeoutMs,
|
|
1454
|
-
...(messageRef ? { messageRef } : {}),
|
|
1455
|
-
output: absurdIntegrationEventBoundaryOutput({
|
|
1554
|
+
return {
|
|
1555
|
+
checkpoint: absurdCheckpointWithIntegrationEvent({
|
|
1556
|
+
checkpoint,
|
|
1557
|
+
boundaryId: boundary.boundaryId,
|
|
1456
1558
|
eventKey: boundary.eventKey,
|
|
1457
|
-
|
|
1458
|
-
messageRef,
|
|
1459
|
-
|
|
1559
|
+
timeoutMs: boundary.timeoutMs,
|
|
1560
|
+
...(messageRef ? { messageRef } : {}),
|
|
1561
|
+
output: absurdIntegrationEventBoundaryOutput({
|
|
1562
|
+
eventKey: boundary.eventKey,
|
|
1563
|
+
signalData: signalData(claimed),
|
|
1564
|
+
messageRef,
|
|
1565
|
+
timedOut: resolvedTimedOut,
|
|
1566
|
+
}),
|
|
1460
1567
|
}),
|
|
1461
|
-
|
|
1568
|
+
providerWake: false,
|
|
1569
|
+
};
|
|
1462
1570
|
}
|
|
1463
1571
|
|
|
1464
1572
|
/**
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-scope.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One quota scope owns both learning evidence and the dispatch budget. Rule
|
|
3
|
+
* rows retain configured ceilings and concurrency; they must not independently
|
|
4
|
+
* learn from copies of the same provider feedback. These CTEs are shared by
|
|
5
|
+
* pure-rate and concurrency-bearing admission, in the same atomic grant SQL.
|
|
6
|
+
*/
|
|
7
|
+
const AIMD_RECOVERY_STEP_MS = 1_000;
|
|
8
|
+
// Increase relative to the learned rate, not by a fixed RPS amount: a long
|
|
9
|
+
// Retry-After can imply a very small quota, where even +1 RPS would be an
|
|
10
|
+
// enormous and repeated over-probe. A 25% clean-success step still recovers a
|
|
11
|
+
// healthy 1->5 RPS provider in eight observations instead of seventeen.
|
|
12
|
+
const AIMD_ADDITIVE_STEP_FRACTION = 0.25;
|
|
13
|
+
|
|
14
|
+
export function acquireRateScopeSql(
|
|
15
|
+
table: string,
|
|
16
|
+
successesParameter: string,
|
|
17
|
+
): string {
|
|
18
|
+
return `provider_hold_rows AS (
|
|
19
|
+
SELECT * FROM ${table} WHERE bucket_id = $1 FOR UPDATE
|
|
20
|
+
), provider_hold AS (
|
|
21
|
+
SELECT h.*,
|
|
22
|
+
LEAST(
|
|
23
|
+
GREATEST(CASE WHEN h.seen_backpressure THEN 1 ELSE h.capacity END, h.refill_per_sec),
|
|
24
|
+
h.tokens + GREATEST(0, nv.now_ms - h.refilled_at_ms) * h.refill_per_sec / 1000.0
|
|
25
|
+
) AS tokens_after_refill,
|
|
26
|
+
${successesParameter}::int = 0
|
|
27
|
+
AND nv.now_ms - GREATEST(h.refilled_at_ms, h.next_dispatch_at_ms, h.retry_at_ms)
|
|
28
|
+
> GREATEST(1000, 2000.0 / h.refill_per_sec) AS observation_was_idle,
|
|
29
|
+
CASE WHEN ${successesParameter}::int > 0
|
|
30
|
+
AND h.rate_observation_started_at_ms IS NOT NULL
|
|
31
|
+
AND nv.now_ms - h.last_ramp_at_ms >= CASE WHEN h.seen_backpressure
|
|
32
|
+
THEN LEAST(h.rate_recovery_interval_ms, ${AIMD_RECOVERY_STEP_MS})
|
|
33
|
+
ELSE h.rate_recovery_interval_ms END
|
|
34
|
+
AND h.retry_at_ms <= nv.now_ms
|
|
35
|
+
THEN LEAST(h.max_rps, CASE WHEN h.seen_backpressure
|
|
36
|
+
THEN h.refill_per_sec * (1 + ${AIMD_ADDITIVE_STEP_FRACTION})
|
|
37
|
+
ELSE h.refill_per_sec * 2 END)
|
|
38
|
+
ELSE h.refill_per_sec END AS next_refill_per_sec,
|
|
39
|
+
CASE WHEN ${successesParameter}::int > 0
|
|
40
|
+
AND h.rate_observation_started_at_ms IS NOT NULL
|
|
41
|
+
AND nv.now_ms - h.last_ramp_at_ms >= CASE WHEN h.seen_backpressure
|
|
42
|
+
THEN LEAST(h.rate_recovery_interval_ms, ${AIMD_RECOVERY_STEP_MS})
|
|
43
|
+
ELSE h.rate_recovery_interval_ms END
|
|
44
|
+
AND h.retry_at_ms <= nv.now_ms
|
|
45
|
+
THEN nv.now_ms ELSE h.last_ramp_at_ms END AS next_ramp_at_ms
|
|
46
|
+
FROM provider_hold_rows h CROSS JOIN now_value nv
|
|
47
|
+
)`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function updateRateScopeSql(table: string): string {
|
|
51
|
+
return `updated_scope AS (
|
|
52
|
+
UPDATE ${table} h SET
|
|
53
|
+
tokens = ph.tokens_after_refill - (SELECT granted FROM grant_calc),
|
|
54
|
+
refilled_at_ms = nv.now_ms,
|
|
55
|
+
refill_per_sec = ph.next_refill_per_sec,
|
|
56
|
+
last_ramp_at_ms = ph.next_ramp_at_ms,
|
|
57
|
+
rate_observation_started_at_ms = CASE
|
|
58
|
+
WHEN (SELECT granted FROM grant_calc) > 0 AND ph.observation_was_idle THEN nv.now_ms
|
|
59
|
+
WHEN (SELECT granted FROM grant_calc) > 0 THEN COALESCE(ph.rate_observation_started_at_ms, nv.now_ms)
|
|
60
|
+
ELSE ph.rate_observation_started_at_ms END,
|
|
61
|
+
rate_successes = CASE
|
|
62
|
+
WHEN (SELECT granted FROM grant_calc) > 0
|
|
63
|
+
AND ph.rate_observation_started_at_ms IS NULL THEN 0
|
|
64
|
+
WHEN (SELECT granted FROM grant_calc) > 0 AND ph.observation_was_idle THEN 0
|
|
65
|
+
ELSE ph.rate_successes END,
|
|
66
|
+
rate_throttle_ceiling_rps = CASE
|
|
67
|
+
WHEN (SELECT granted FROM grant_calc) > 0
|
|
68
|
+
AND ph.rate_observation_started_at_ms IS NULL THEN NULL
|
|
69
|
+
ELSE ph.rate_throttle_ceiling_rps END,
|
|
70
|
+
next_dispatch_at_ms = CASE WHEN (SELECT granted FROM grant_calc) > 0 THEN
|
|
71
|
+
(SELECT first_at_ms FROM schedule_calc)
|
|
72
|
+
+ CEIL((SELECT granted FROM grant_calc)::numeric / (SELECT burst_size FROM schedule_calc))::bigint
|
|
73
|
+
* (SELECT spacing_ms FROM schedule_calc) * (SELECT burst_size FROM schedule_calc)
|
|
74
|
+
ELSE ph.next_dispatch_at_ms END
|
|
75
|
+
FROM provider_hold ph CROSS JOIN now_value nv
|
|
76
|
+
WHERE h.bucket_id = $1
|
|
77
|
+
RETURNING h.bucket_id, h.rate_observation_started_at_ms, h.rate_successes,
|
|
78
|
+
h.rate_recovery_interval_ms, h.seen_backpressure
|
|
79
|
+
)`;
|
|
80
|
+
}
|