deepline 0.3.184 → 0.3.186

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.
Files changed (42) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/sdk/src/types.ts +4 -0
  3. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +53 -0
  4. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +751 -193
  5. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +4 -0
  6. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +3 -1
  7. package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +1 -0
  8. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +437 -150
  9. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +47 -4
  10. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +3 -1
  11. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +18 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/launch-identity.ts +229 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +3 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/contracts.ts +16 -1
  15. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/postgres-adapter.ts +3 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +22 -2
  17. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +6 -1
  18. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +11 -5
  19. package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +102 -12
  20. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +8 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/runtime-telemetry.ts +9 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +7 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +136 -28
  24. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-scope.ts +80 -0
  25. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +897 -207
  26. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +271 -25
  27. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +21 -0
  28. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +120 -34
  29. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +82 -2
  30. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +7 -2
  31. package/dist/cli/index.js +29 -3
  32. package/dist/cli/index.mjs +29 -3
  33. package/dist/index.d.mts +4 -0
  34. package/dist/index.d.ts +4 -0
  35. package/dist/index.js +1 -1
  36. package/dist/index.mjs +1 -1
  37. package/dist/install-integrity.json +2 -0
  38. package/dist/release.d.mts +1 -1
  39. package/dist/release.d.ts +1 -1
  40. package/dist/release.js +1 -1
  41. package/dist/release.mjs +1 -1
  42. package/package.json +1 -1
@@ -85,6 +85,8 @@ export function createRuntimeFlow<Input>(input: {
85
85
  export type RuntimeDispatchProviderPermit = RuntimeFlowLease & {
86
86
  cancel(): Promise<void>;
87
87
  park?(): Promise<void>;
88
+ /** Recheck a shared provider hold at the physical dispatch boundary. */
89
+ validate?(): Promise<boolean>;
88
90
  };
89
91
 
90
92
  export type RuntimeDispatchFlowInput = {
@@ -92,6 +94,11 @@ export type RuntimeDispatchFlowInput = {
92
94
  signal?: AbortSignal;
93
95
  /** Fixtures consume provider capacity but acquire app transport after delay. */
94
96
  requiresTransportPermit: boolean;
97
+ /**
98
+ * Defer shared-hold validation until the request callback reaches its actual
99
+ * outbound send. The callback must call the supplied boundary exactly once.
100
+ */
101
+ deferProviderPermitValidation?: boolean;
95
102
  /** Emits the request-side transport wait observed by the caller's metrics. */
96
103
  onTransportAdmitted?: (waitMs: number) => void;
97
104
  };
@@ -114,12 +121,18 @@ type RuntimeDispatchPermit<
114
121
  */
115
122
  export type RuntimeDispatchScope = {
116
123
  /**
117
- * Run the one physical request. `beforeRelease` executes after the response
118
- * is known but while the provider lease is still valid, so a caller can
119
- * durably record response-bound feedback that the lease authorizes.
124
+ * Run the one physical request. Deferred-validation callers must invoke
125
+ * `beforeProviderDispatch` immediately before the outbound send, after any
126
+ * async setup/queueing. `beforeRelease` executes after the response is known
127
+ * but while the provider lease is still valid, so a caller can durably
128
+ * record response-bound feedback that the lease authorizes.
120
129
  */
121
130
  request<Output>(
122
- work: () => Output | Promise<Output>,
131
+ work: (
132
+ beforeProviderDispatch: (
133
+ onPermitInvalidated?: () => void,
134
+ ) => boolean | Promise<boolean>,
135
+ ) => Output | Promise<Output>,
123
136
  beforeRelease?: () => void | Promise<void>,
124
137
  ): Promise<Output>;
125
138
  };
@@ -130,10 +143,12 @@ export type RuntimeDispatchFlow<
130
143
  /**
131
144
  * Admit and run exactly one physical provider request.
132
145
  *
133
- * Work that exits before `request` returns the provider permit through its
134
- * cancellation path. Once `request` starts, the permit is released after
135
- * the request body is consumed, even if that body cannot be verified. This
136
- * preserves the ambiguity boundary while making resource lifetime private.
146
+ * Work must call `beforeProviderDispatch` at the actual outbound-send edge.
147
+ * That is where shared Retry-After state is checked: not before async
148
+ * request preparation. Work that exits before that call returns the provider
149
+ * permit through its cancellation path. Once the send starts, the permit is
150
+ * released after the response body is consumed, even if it cannot be
151
+ * verified.
137
152
  */
138
153
  run<Output>(
139
154
  input: RuntimeDispatchFlowInput,
@@ -264,8 +279,10 @@ export function createRuntimeDispatchFlow<
264
279
  admission: RuntimeDispatchFlowInput,
265
280
  work: (scope: RuntimeDispatchScope) => Output | Promise<Output>,
266
281
  ): Promise<Output> {
267
- const permit = await admit(admission);
282
+ let permit = await admit(admission);
268
283
  let transportLease = permit.integrationRequestLease;
284
+ let requiresTransportPermitForReacquire =
285
+ admission.requiresTransportPermit;
269
286
  let requestStarted = false;
270
287
  let requestUsed = false;
271
288
  let scopeOpen = true;
@@ -284,7 +301,11 @@ export function createRuntimeDispatchFlow<
284
301
  try {
285
302
  const output = await work({
286
303
  request: <RequestOutput>(
287
- request: () => RequestOutput | Promise<RequestOutput>,
304
+ request: (
305
+ beforeProviderDispatch: (
306
+ onPermitInvalidated?: () => void,
307
+ ) => boolean | Promise<boolean>,
308
+ ) => RequestOutput | Promise<RequestOutput>,
288
309
  beforeRelease?: () => void | Promise<void>,
289
310
  ): Promise<RequestOutput> => {
290
311
  if (!scopeOpen) {
@@ -301,10 +322,69 @@ export function createRuntimeDispatchFlow<
301
322
  }
302
323
  requestUsed = true;
303
324
  requestPromise = (async () => {
325
+ let dispatchStarted = false;
326
+ const beforeProviderDispatch = (
327
+ onPermitInvalidated?: () => void,
328
+ ): boolean | Promise<boolean> => {
329
+ if (dispatchStarted) {
330
+ throw new Error(
331
+ 'Runtime dispatch flow permits exactly one physical provider dispatch.',
332
+ );
333
+ }
334
+ if (!permit.providerPermit.validate) {
335
+ // Preserve the zero-round-trip in-memory path: deferred
336
+ // callers still cross the exact send boundary, but do not
337
+ // pay an artificial microtask when there is nothing shared
338
+ // to revalidate.
339
+ dispatchStarted = true;
340
+ requestStarted = true;
341
+ return false;
342
+ }
343
+ return validateAndStartProviderDispatch(onPermitInvalidated);
344
+ };
345
+ const validateAndStartProviderDispatch = async (
346
+ onPermitInvalidated?: () => void,
347
+ ): Promise<boolean> => {
348
+ let replaced = false;
349
+ while (
350
+ permit.providerPermit.validate &&
351
+ !(await permit.providerPermit.validate())
352
+ ) {
353
+ requestStarted = false;
354
+ onPermitInvalidated?.();
355
+ await releasePermit();
356
+ permit = await admit({
357
+ ...admission,
358
+ requiresTransportPermit:
359
+ requiresTransportPermitForReacquire,
360
+ });
361
+ transportLease = permit.integrationRequestLease;
362
+ requestStarted = false;
363
+ permitReleased = false;
364
+ replaced = true;
365
+ }
366
+ dispatchStarted = true;
367
+ if (admission.deferProviderPermitValidation) {
368
+ requestStarted = true;
369
+ }
370
+ return replaced;
371
+ };
304
372
  try {
373
+ if (!admission.deferProviderPermitValidation) {
374
+ // Preserve the ordinary API: its request callback begins
375
+ // the physical send immediately. Callers with asynchronous
376
+ // preparation opt into deferred validation and invoke this
377
+ // same boundary at the final outbound edge.
378
+ if (permit.providerPermit.validate) {
379
+ await beforeProviderDispatch();
380
+ } else {
381
+ dispatchStarted = true;
382
+ }
383
+ }
305
384
  if (transportLease) {
306
385
  admission.onTransportAdmitted?.(0);
307
386
  } else {
387
+ requiresTransportPermitForReacquire = true;
308
388
  const transportStartedAt = Date.now();
309
389
  transportLease = await input.waitForTransportSlot({
310
390
  signal: admission.signal,
@@ -313,8 +393,18 @@ export function createRuntimeDispatchFlow<
313
393
  Date.now() - transportStartedAt,
314
394
  );
315
395
  }
316
- requestStarted = true;
317
- return await request();
396
+ if (!admission.deferProviderPermitValidation) {
397
+ // Preserve the cancellation boundary for a permit whose
398
+ // transport wait fails before the request callback begins.
399
+ requestStarted = true;
400
+ }
401
+ const result = await request(beforeProviderDispatch);
402
+ if (!dispatchStarted) {
403
+ throw new Error(
404
+ 'Runtime dispatch request completed without crossing its provider-dispatch boundary.',
405
+ );
406
+ }
407
+ return result;
318
408
  } finally {
319
409
  // Feedback is response-bound, but it is not allowed to strand
320
410
  // the capacity that made the response possible. Preserve the
@@ -47,6 +47,7 @@ export const RUNTIME_OPERATION_CAPABILITIES = {
47
47
  rate_state_acquire: 'rate.acquire',
48
48
  rate_state_release: 'rate.acquire',
49
49
  rate_state_penalize: 'rate.acquire',
50
+ rate_state_validate: 'rate.acquire',
50
51
  get_runtime_traffic_policy: 'rate.acquire',
51
52
  governor_budget_charge: 'budget.charge',
52
53
  resolve_secret: 'secret.resolve',
@@ -69,6 +70,13 @@ export const RUNTIME_OPERATION_CONTRACT: DeployedOperationContract =
69
70
  kind: 'equivalent-fallback',
70
71
  action: 'complete_runtime_step_receipts',
71
72
  },
73
+ // Permit validation cannot safely fall back to a cached ticket: an old
74
+ // receiver would ignore the durable provider Retry-After hold. New
75
+ // workers therefore fail closed with this typed compatibility result.
76
+ rate_state_validate: {
77
+ kind: 'feature-unavailable',
78
+ code: 'RATE_STATE_VALIDATE_UNAVAILABLE',
79
+ },
72
80
  },
73
81
  });
74
82
 
@@ -41,6 +41,12 @@ export type PlayRunLaunchConflictTelemetryInput = {
41
41
  artifactHash?: string | null;
42
42
  graphHash?: string | null;
43
43
  inputSha256?: string | null;
44
+ /** Named-intent digest of the incoming durable launch envelope. */
45
+ launchIdentityDigest?: string | null;
46
+ /** Admission-side conflict classification when available. */
47
+ conflictReason?: string | null;
48
+ /** Named launch-intent fields that differed, when the persisted envelope is available. */
49
+ conflictingFields?: readonly string[] | null;
44
50
  error?: unknown;
45
51
  };
46
52
 
@@ -113,6 +119,9 @@ export function buildPlayRunLaunchConflictTelemetry(
113
119
  revisionId: input.revisionId ?? null,
114
120
  artifactHash: input.artifactHash ?? null,
115
121
  graphHash: input.graphHash ?? null,
122
+ launchIdentityDigest: input.launchIdentityDigest ?? null,
123
+ conflictReason: input.conflictReason ?? null,
124
+ conflictingFields: input.conflictingFields ?? null,
116
125
  triage:
117
126
  level === 'error'
118
127
  ? {
@@ -216,6 +216,13 @@ export type PlaySchedulerSubmitInput = {
216
216
  runtimeTestFaultHeader?: string | null;
217
217
  /** Request-scoped, dev-only runtime policy overrides for black-box durability tests. */
218
218
  testPolicyOverrides?: RuntimeTestPolicyOverrides | null;
219
+ /**
220
+ * Digest of the named intent behind this launch. Runtime admission compares
221
+ * this optional field instead of treating transport and execution details as
222
+ * immutable identity. Older persisted rows omit it and keep the legacy
223
+ * comparison until they are gone.
224
+ */
225
+ launchIdentityDigest?: string | null;
219
226
  /** Millisecond epoch timestamp captured immediately before scheduler submit. */
220
227
  submittedAtMs?: number;
221
228
  /** Scheduler backend that owns orchestration for this run. */
@@ -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
- resolvedCheckpoint = await resolveIntegrationEvent({
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 = await input.claimSignal(boundary.eventKey, boundary.boundaryId);
1458
+ const early = postTimeoutPark
1459
+ ? null
1460
+ : await input.claimSignal(boundary.eventKey, boundary.boundaryId);
1409
1461
  if (early) {
1410
- return absurdCheckpointWithIntegrationEvent({
1411
- checkpoint,
1412
- boundaryId: boundary.boundaryId,
1413
- eventKey: boundary.eventKey,
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
- signalData: signalData(early),
1419
- messageRef,
1420
- timedOut: false,
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
- stepName: boundary.boundaryId,
1430
- timeout: Math.max(0, boundary.timeoutMs) / 1000,
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 absurdCheckpointWithIntegrationEvent({
1450
- checkpoint,
1451
- boundaryId: boundary.boundaryId,
1452
- eventKey: boundary.eventKey,
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
- signalData: signalData(claimed),
1458
- messageRef,
1459
- timedOut: resolvedTimedOut,
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
  /**
@@ -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
+ }