deepline 0.3.289 → 0.3.291
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/client.ts +8 -6
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/runs/overview.ts +16 -6
- package/dist/bundling-sources/sdk/src/types.ts +21 -2
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +41 -11
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +317 -63
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +10 -2
- package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/launch-identity.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/log-context.ts +36 -0
- package/dist/bundling-sources/shared_libs/play-runtime/log-provenance.ts +19 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/worker-terminal-projection.ts +6 -5
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +12 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-event-catalog.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger-event-transport.ts +74 -33
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +73 -3
- package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +39 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +72 -9
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +2 -3
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +77 -10
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-pool-options.ts +145 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +17 -9
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -139
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +19 -5
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +176 -18
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +7 -5
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1404 -478
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +55 -25
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-dispatch-authority.ts +97 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +34 -15
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +558 -57
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +10 -1
- package/dist/bundling-sources/shared_libs/play-runtime/step-progress.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/test-admission-batching.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +22 -5
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +35 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +68 -8
- package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/zerobounce-batching.ts +75 -28
- package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +2 -0
- package/dist/cli/index.js +195 -582
- package/dist/cli/index.mjs +195 -582
- package/dist/{client-FR_u0dxT.d.ts → client-Ct4jmvAg.d.ts} +29 -8
- package/dist/{client-phK1Ixcl.d.mts → client-Ct9GKrwu.d.mts} +29 -8
- package/dist/{compiler-manifest-9SQtUuKb.d.mts → compiler-manifest-qtTNYvBl.d.mts} +2 -0
- package/dist/{compiler-manifest-9SQtUuKb.d.ts → compiler-manifest-qtTNYvBl.d.ts} +2 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +14 -5
- package/dist/index.mjs +14 -5
- package/dist/install-integrity.json +7 -4
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/run-request.d.mts +2 -2
- package/dist/plays/run-request.d.ts +2 -2
- 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
|
@@ -11,7 +11,6 @@ import { RUN_EVENT_KIND } from '../run-event-catalog';
|
|
|
11
11
|
* absurd-specific wiring (queue, event names, checkpoint boundary shaping).
|
|
12
12
|
*/
|
|
13
13
|
import { createHash } from 'node:crypto';
|
|
14
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
15
14
|
import type {
|
|
16
15
|
PlayCheckpoint,
|
|
17
16
|
PlayExecutionEvent,
|
|
@@ -22,11 +21,6 @@ import type { PlayRunLedgerEvent } from '../run-ledger';
|
|
|
22
21
|
import type { PlaySchedulerSubmitInput } from '../scheduler-backend';
|
|
23
22
|
import type { WorkerRuntimeApiContext } from '../app-runtime-api';
|
|
24
23
|
import { RUNTIME_CAPACITY_POLICY } from '../runtime-capacity-policy';
|
|
25
|
-
import {
|
|
26
|
-
PLAY_ADMISSION_MAX_ACQUIRE_QUEUE_WAIT_MS,
|
|
27
|
-
PLAY_ADMISSION_MAX_ACTIVE_LIFECYCLES,
|
|
28
|
-
PLAY_ADMISSION_MAX_PARALLELISM,
|
|
29
|
-
} from '../admission-transport-policy';
|
|
30
24
|
import { assertKnownResolvedBoundary } from '../checkpoint-boundaries';
|
|
31
25
|
import type { Absurd, Log, RetryStrategy } from '@deepline/absurd/sdk';
|
|
32
26
|
import { isIsolatedRuntimeSchedulerSchema } from '../runtime-scheduler-topology';
|
|
@@ -37,8 +31,6 @@ import {
|
|
|
37
31
|
} from './postgres';
|
|
38
32
|
import { createAbsurdForRuntimeScheduler } from './absurd-pool';
|
|
39
33
|
import {
|
|
40
|
-
RUNTIME_SCHEDULER_CONTROL_PLANE_MAX_CLIENTS,
|
|
41
|
-
resolveWorkflowRuntimePostgresUrl,
|
|
42
34
|
withRuntimeSchedulerClient,
|
|
43
35
|
type RuntimeSchedulerPoolOptions,
|
|
44
36
|
} from './postgres-pool';
|
|
@@ -548,137 +540,14 @@ export type AbsurdRuntimeResult = {
|
|
|
548
540
|
error?: string;
|
|
549
541
|
};
|
|
550
542
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
* Vercel Fluid invocations share a process and its scheduler pool. A single
|
|
560
|
-
* client serializes unrelated launches, reads, and critical lease lookups;
|
|
561
|
-
* one held connection can make every other request hit its acquire timeout.
|
|
562
|
-
* Use the shared two-client control-plane budget so another request can make
|
|
563
|
-
* progress while keeping each instance's PgBouncer footprint bounded.
|
|
564
|
-
*/
|
|
565
|
-
export const ABSURD_SERVERLESS_POOL_MAX_CLIENTS =
|
|
566
|
-
RUNTIME_SCHEDULER_CONTROL_PLANE_MAX_CLIENTS;
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* A Vercel isolate may survive long after a webhook has returned. Keep its
|
|
570
|
-
* scheduler clients only long enough to reuse them for an immediate follow-up,
|
|
571
|
-
* then return idle PgBouncer clients before the source's delayed Workpool
|
|
572
|
-
* recovery is due. The worker fleet keeps the ordinary 30-second idle lifetime.
|
|
573
|
-
*/
|
|
574
|
-
export const ABSURD_SERVERLESS_POOL_IDLE_TIMEOUT_MS = 1_000;
|
|
575
|
-
|
|
576
|
-
// Ordinary Vercel requests share the bounded control-plane pool above. The
|
|
577
|
-
// private batch adapter is different: Workpool elects one global up-to-1,024-
|
|
578
|
-
// source collection before entering this context, so one batch request may use
|
|
579
|
-
// its larger bounded scheduler capacity without multiplying it per source.
|
|
580
|
-
// Other Workpool adapters can still overlap during rollout.
|
|
581
|
-
type ServerlessAdmissionPoolContext = {
|
|
582
|
-
maxClients: number;
|
|
583
|
-
maxAcquireQueueDepth: number;
|
|
584
|
-
maxAcquireQueueWaitMs: number;
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
const serverlessAdmissionPoolContext =
|
|
588
|
-
new AsyncLocalStorage<ServerlessAdmissionPoolContext>();
|
|
589
|
-
|
|
590
|
-
export async function withServerlessAdmissionPoolMaxClients<T>(input: {
|
|
591
|
-
maxClients: number;
|
|
592
|
-
maxAcquireQueueDepth?: number;
|
|
593
|
-
maxAcquireQueueWaitMs?: number;
|
|
594
|
-
fn: () => Promise<T>;
|
|
595
|
-
}): Promise<T> {
|
|
596
|
-
if (
|
|
597
|
-
!Number.isInteger(input.maxClients) ||
|
|
598
|
-
input.maxClients < 1 ||
|
|
599
|
-
input.maxClients > PLAY_ADMISSION_MAX_PARALLELISM
|
|
600
|
-
) {
|
|
601
|
-
throw new Error(
|
|
602
|
-
'Serverless admission pool capacity is outside the supported range.',
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
const maxAcquireQueueDepth =
|
|
606
|
-
input.maxAcquireQueueDepth ?? PLAY_ADMISSION_MAX_ACTIVE_LIFECYCLES;
|
|
607
|
-
const maxAcquireQueueWaitMs =
|
|
608
|
-
input.maxAcquireQueueWaitMs ?? PLAY_ADMISSION_MAX_ACQUIRE_QUEUE_WAIT_MS;
|
|
609
|
-
if (
|
|
610
|
-
!Number.isSafeInteger(maxAcquireQueueDepth) ||
|
|
611
|
-
maxAcquireQueueDepth < 1 ||
|
|
612
|
-
maxAcquireQueueDepth > PLAY_ADMISSION_MAX_ACTIVE_LIFECYCLES
|
|
613
|
-
) {
|
|
614
|
-
throw new Error(
|
|
615
|
-
'Serverless admission acquire queue capacity is outside the supported range.',
|
|
616
|
-
);
|
|
617
|
-
}
|
|
618
|
-
if (
|
|
619
|
-
!Number.isSafeInteger(maxAcquireQueueWaitMs) ||
|
|
620
|
-
maxAcquireQueueWaitMs < 5_000 ||
|
|
621
|
-
maxAcquireQueueWaitMs > PLAY_ADMISSION_MAX_ACQUIRE_QUEUE_WAIT_MS
|
|
622
|
-
) {
|
|
623
|
-
throw new Error(
|
|
624
|
-
'Serverless admission acquire queue wait is outside the supported range.',
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
return await serverlessAdmissionPoolContext.run(
|
|
628
|
-
{
|
|
629
|
-
maxClients: input.maxClients,
|
|
630
|
-
maxAcquireQueueDepth,
|
|
631
|
-
maxAcquireQueueWaitMs,
|
|
632
|
-
},
|
|
633
|
-
input.fn,
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
/**
|
|
638
|
-
* The private batch adapter owns this request-local scheduler pool. Reserve
|
|
639
|
-
* one client for executor-token and other critical control-plane traffic; the
|
|
640
|
-
* rest may submit the independently idempotent batch sources in parallel.
|
|
641
|
-
*/
|
|
642
|
-
export function serverlessAdmissionSubmitMaxActive(): number | null {
|
|
643
|
-
const context = serverlessAdmissionPoolContext.getStore();
|
|
644
|
-
return context === undefined ? null : Math.max(1, context.maxClients - 1);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
/**
|
|
648
|
-
* Resolve the runtime scheduler Postgres pool options for the absurd control
|
|
649
|
-
* plane. Loud failure over silent fallback: the absurd worker and scheduler
|
|
650
|
-
* only run under Node where `WORKFLOW_RUNTIME_POSTGRES` is configured.
|
|
651
|
-
*/
|
|
652
|
-
export function resolveAbsurdPoolOptions(
|
|
653
|
-
explicitUrl?: string | null,
|
|
654
|
-
): RuntimeSchedulerPoolOptions {
|
|
655
|
-
const url = explicitUrl?.trim() || resolveWorkflowRuntimePostgresUrl();
|
|
656
|
-
if (!url) {
|
|
657
|
-
throw new Error(
|
|
658
|
-
'absurd scheduler backend requires WORKFLOW_RUNTIME_POSTGRES (runtime ' +
|
|
659
|
-
'scheduler Postgres URL). Refusing to fall back to local execution.',
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
const serverless = process.env.VERCEL?.trim();
|
|
663
|
-
const context = serverlessAdmissionPoolContext.getStore();
|
|
664
|
-
return {
|
|
665
|
-
url,
|
|
666
|
-
applicationName: 'deepline-absurd-runtime',
|
|
667
|
-
maxClients:
|
|
668
|
-
serverless || context !== undefined
|
|
669
|
-
? (context?.maxClients ?? ABSURD_SERVERLESS_POOL_MAX_CLIENTS)
|
|
670
|
-
: ABSURD_RUNTIME_POOL_MAX_CLIENTS,
|
|
671
|
-
...(serverless || context !== undefined
|
|
672
|
-
? { idleTimeoutMs: ABSURD_SERVERLESS_POOL_IDLE_TIMEOUT_MS }
|
|
673
|
-
: {}),
|
|
674
|
-
...(context
|
|
675
|
-
? {
|
|
676
|
-
maxAcquireQueueDepth: context.maxAcquireQueueDepth,
|
|
677
|
-
maxAcquireQueueWaitMs: context.maxAcquireQueueWaitMs,
|
|
678
|
-
}
|
|
679
|
-
: {}),
|
|
680
|
-
};
|
|
681
|
-
}
|
|
543
|
+
export {
|
|
544
|
+
ABSURD_RUNTIME_POOL_MAX_CLIENTS,
|
|
545
|
+
ABSURD_SERVERLESS_POOL_MAX_CLIENTS,
|
|
546
|
+
ABSURD_SERVERLESS_POOL_IDLE_TIMEOUT_MS,
|
|
547
|
+
withServerlessAdmissionPoolMaxClients,
|
|
548
|
+
serverlessAdmissionSubmitMaxActive,
|
|
549
|
+
resolveAbsurdPoolOptions,
|
|
550
|
+
} from './absurd-pool-options';
|
|
682
551
|
|
|
683
552
|
export function normalizeAbsurdSchedulerOptions(
|
|
684
553
|
options: { url?: string | null; schema?: string | null } = {},
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* connection. Notifications are strictly a wakeup hint — the tables remain
|
|
23
23
|
* the source of truth and every waiter still reads the DB.
|
|
24
24
|
*/
|
|
25
|
+
import { attachDatabasePool } from '@vercel/functions/db-connections';
|
|
25
26
|
import { Client, Pool } from 'pg';
|
|
26
27
|
import {
|
|
27
28
|
RuntimeSchedulerPgBouncerCapacityError,
|
|
@@ -175,6 +176,8 @@ export const RUNTIME_SCHEDULER_POOL_DEFAULTS = {
|
|
|
175
176
|
keepAliveIntervalMs: 60_000,
|
|
176
177
|
} as const;
|
|
177
178
|
|
|
179
|
+
export const RUNTIME_SCHEDULER_SERVERLESS_IDLE_TIMEOUT_MS = 1_000;
|
|
180
|
+
|
|
178
181
|
/**
|
|
179
182
|
* A control-plane process only needs a few short scheduler transactions at
|
|
180
183
|
* once. This shared cap keeps horizontally scaled workers and the receipt
|
|
@@ -197,8 +200,8 @@ export type RuntimeSchedulerPoolOptions = {
|
|
|
197
200
|
maxClients?: number;
|
|
198
201
|
/**
|
|
199
202
|
* Serverless launchers must not pin a scarce PgBouncer client between
|
|
200
|
-
* requests.
|
|
201
|
-
*
|
|
203
|
+
* requests. Vercel pools share one short idle lifetime; this override only
|
|
204
|
+
* applies to long-lived processes.
|
|
202
205
|
*/
|
|
203
206
|
idleTimeoutMs?: number;
|
|
204
207
|
/**
|
|
@@ -447,6 +450,7 @@ export class RuntimeSchedulerPoolAdmission {
|
|
|
447
450
|
|
|
448
451
|
type PoolEntry = {
|
|
449
452
|
pool: Pool;
|
|
453
|
+
databasePoolAttached?: boolean;
|
|
450
454
|
maxClients: number;
|
|
451
455
|
maxAcquireQueueDepth: number;
|
|
452
456
|
admission: RuntimeSchedulerPoolAdmission;
|
|
@@ -723,7 +727,7 @@ function getPoolEntry(options: RuntimeSchedulerPoolOptions): PoolEntry {
|
|
|
723
727
|
assertRuntimeSchedulerHotPathUrl(options.url);
|
|
724
728
|
const maxClients =
|
|
725
729
|
options.maxClients ?? RUNTIME_SCHEDULER_POOL_DEFAULTS.maxClients;
|
|
726
|
-
const
|
|
730
|
+
const requestedIdleTimeoutMs =
|
|
727
731
|
options.idleTimeoutMs ?? RUNTIME_SCHEDULER_POOL_DEFAULTS.idleTimeoutMs;
|
|
728
732
|
const maxAcquireQueueDepth =
|
|
729
733
|
options.maxAcquireQueueDepth ??
|
|
@@ -738,11 +742,17 @@ function getPoolEntry(options: RuntimeSchedulerPoolOptions): PoolEntry {
|
|
|
738
742
|
'instead of widening a single process pool.',
|
|
739
743
|
);
|
|
740
744
|
}
|
|
741
|
-
if (
|
|
745
|
+
if (
|
|
746
|
+
!Number.isSafeInteger(requestedIdleTimeoutMs) ||
|
|
747
|
+
requestedIdleTimeoutMs < 0
|
|
748
|
+
) {
|
|
742
749
|
throw new Error(
|
|
743
|
-
`Runtime scheduler pool idleTimeoutMs=${
|
|
750
|
+
`Runtime scheduler pool idleTimeoutMs=${requestedIdleTimeoutMs} must be a non-negative integer.`,
|
|
744
751
|
);
|
|
745
752
|
}
|
|
753
|
+
const idleTimeoutMs = process.env.VERCEL?.trim()
|
|
754
|
+
? RUNTIME_SCHEDULER_SERVERLESS_IDLE_TIMEOUT_MS
|
|
755
|
+
: requestedIdleTimeoutMs;
|
|
746
756
|
if (
|
|
747
757
|
!Number.isSafeInteger(maxAcquireQueueDepth) ||
|
|
748
758
|
maxAcquireQueueDepth < 1 ||
|
|
@@ -1049,6 +1059,10 @@ export async function withRuntimeSchedulerClient<T>(
|
|
|
1049
1059
|
let queryCount = 0;
|
|
1050
1060
|
let queryMs = 0;
|
|
1051
1061
|
try {
|
|
1062
|
+
if (!entry.databasePoolAttached) {
|
|
1063
|
+
attachDatabasePool(entry.pool);
|
|
1064
|
+
entry.databasePoolAttached = true;
|
|
1065
|
+
}
|
|
1052
1066
|
throwIfSchedulerAborted(signal);
|
|
1053
1067
|
const result = await fn({
|
|
1054
1068
|
query: async (sql, params) => {
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { RUN_EVENT_KIND } from '../run-event-catalog';
|
|
|
14
14
|
* Runtime Sheet row durability belongs to the context-level row writer.
|
|
15
15
|
*/
|
|
16
16
|
import { PlayStepLifecycleTracker } from '../step-lifecycle-tracker';
|
|
17
|
+
import { isDurableDirectRunnerLog } from '../log-provenance';
|
|
17
18
|
import {
|
|
18
19
|
updateRunStatusViaAppRuntime,
|
|
19
20
|
type RuntimeStatusUpdate,
|
|
@@ -23,6 +24,8 @@ import type { PlayExecutionEvent } from '../ctx-types';
|
|
|
23
24
|
import type { PlayActivityObservation } from '../activity-observation';
|
|
24
25
|
import {
|
|
25
26
|
normalizePlayLogContext,
|
|
27
|
+
normalizePlayLogMetadata,
|
|
28
|
+
type PlayLogMetadata,
|
|
26
29
|
type PlayLogContext,
|
|
27
30
|
} from '../log-context';
|
|
28
31
|
import type {
|
|
@@ -61,6 +64,7 @@ const PLAY_TOOL_BATCH_LOG_PATTERN =
|
|
|
61
64
|
/(?:^|\]\s*)Executing tool batch\s+([A-Za-z0-9_:-]+):\s+(\d+)\s+calls\b/i;
|
|
62
65
|
const CTX_LOG_TIMESTAMP_PREFIX_RE = /^\[\d{4}-\d{2}-\d{2}T[\d:.]+Z\]\s+/;
|
|
63
66
|
const LIVE_LOG_TAIL_LIMIT = 1_000;
|
|
67
|
+
const STEP_FAILURE_PROGRESS_DEBOUNCE_MS = 250;
|
|
64
68
|
|
|
65
69
|
function docflowInvocationSequence(invocationId?: string): number {
|
|
66
70
|
if (!invocationId) return -1;
|
|
@@ -256,6 +260,11 @@ export type PostgresSchedulerProgressReporter = {
|
|
|
256
260
|
skipTerminalLogReplay?: boolean;
|
|
257
261
|
},
|
|
258
262
|
): Promise<void>;
|
|
263
|
+
/** Fold observed step failures into the terminal outbox instead of a
|
|
264
|
+
* separate, immediately-preceding progress write. */
|
|
265
|
+
collectTerminalFailureProgressEvents?(): PlayRunLedgerEvent[];
|
|
266
|
+
/** Cancel deferred failure progress once the terminal transaction owns it. */
|
|
267
|
+
markTerminalFailureProgressPersisted?(): void;
|
|
259
268
|
};
|
|
260
269
|
|
|
261
270
|
export type PostgresSchedulerDurableLogMode =
|
|
@@ -268,18 +277,7 @@ function shouldPublishDurableLogEvent(
|
|
|
268
277
|
| Pick<PlayRunnerLogEvent, 'source' | 'line'>
|
|
269
278
|
| { source?: unknown; line?: unknown },
|
|
270
279
|
): boolean {
|
|
271
|
-
|
|
272
|
-
// create/upload/execute). Keep those in worker logs while still storing
|
|
273
|
-
// play-authored ctx.log events in the durable Run Log Stream. The one
|
|
274
|
-
// exception is the structured performance marker: it is bounded (one
|
|
275
|
-
// terminal phase observation per runner phase), run-scoped, and is the
|
|
276
|
-
// canonical black-box regression signal for dispatcher throughput. Do not
|
|
277
|
-
// broaden this to arbitrary worker output.
|
|
278
|
-
return (
|
|
279
|
-
mode !== 'direct_worker' ||
|
|
280
|
-
event.source !== 'worker' ||
|
|
281
|
-
(typeof event.line === 'string' && event.line.startsWith('[perf-trace] '))
|
|
282
|
-
);
|
|
280
|
+
return mode !== 'direct_worker' || isDurableDirectRunnerLog(event);
|
|
283
281
|
}
|
|
284
282
|
|
|
285
283
|
function shouldPublishTerminalResultLog(
|
|
@@ -305,14 +303,33 @@ type TerminalLogReplayPayload = NonNullable<
|
|
|
305
303
|
function terminalReplayEntriesForMode(input: {
|
|
306
304
|
resultLogs: readonly string[];
|
|
307
305
|
resultLogContexts?: readonly (PlayLogContext | null)[];
|
|
306
|
+
resultLogMetadata?: readonly (PlayLogMetadata | null)[];
|
|
307
|
+
resultLogIds?: readonly string[];
|
|
308
308
|
durableLogMode: PostgresSchedulerDurableLogMode;
|
|
309
|
-
}): Array<{
|
|
309
|
+
}): Array<{
|
|
310
|
+
line: string;
|
|
311
|
+
context: PlayLogContext | null;
|
|
312
|
+
metadata: PlayLogMetadata | null;
|
|
313
|
+
entryId: string | null;
|
|
314
|
+
}> {
|
|
310
315
|
if (
|
|
311
316
|
input.resultLogContexts !== undefined &&
|
|
312
317
|
input.resultLogContexts.length !== input.resultLogs.length
|
|
313
318
|
) {
|
|
314
319
|
throw new TypeError('Terminal log contexts must align with result logs.');
|
|
315
320
|
}
|
|
321
|
+
if (
|
|
322
|
+
input.resultLogMetadata !== undefined &&
|
|
323
|
+
input.resultLogMetadata.length !== input.resultLogs.length
|
|
324
|
+
) {
|
|
325
|
+
throw new TypeError('Terminal log metadata must align with result logs.');
|
|
326
|
+
}
|
|
327
|
+
if (
|
|
328
|
+
input.resultLogIds &&
|
|
329
|
+
input.resultLogIds.length !== input.resultLogs.length
|
|
330
|
+
) {
|
|
331
|
+
throw new TypeError('Terminal log identities must align with result logs.');
|
|
332
|
+
}
|
|
316
333
|
return input.resultLogs.flatMap((rawLine, index) => {
|
|
317
334
|
const line = rawLine.trim();
|
|
318
335
|
if (
|
|
@@ -325,6 +342,8 @@ function terminalReplayEntriesForMode(input: {
|
|
|
325
342
|
{
|
|
326
343
|
line,
|
|
327
344
|
context: normalizePlayLogContext(input.resultLogContexts?.[index]),
|
|
345
|
+
metadata: normalizePlayLogMetadata(input.resultLogMetadata?.[index]),
|
|
346
|
+
entryId: input.resultLogIds?.[index] ?? null,
|
|
328
347
|
},
|
|
329
348
|
];
|
|
330
349
|
});
|
|
@@ -333,6 +352,8 @@ function terminalReplayEntriesForMode(input: {
|
|
|
333
352
|
function terminalReplayPayloadForMode(input: {
|
|
334
353
|
resultLogs: readonly string[];
|
|
335
354
|
resultLogContexts?: readonly (PlayLogContext | null)[];
|
|
355
|
+
resultLogMetadata?: readonly (PlayLogMetadata | null)[];
|
|
356
|
+
resultLogIds?: readonly string[];
|
|
336
357
|
durableLogMode: PostgresSchedulerDurableLogMode;
|
|
337
358
|
liveLogs: readonly string[];
|
|
338
359
|
liveLogContexts: readonly (PlayLogContext | null)[];
|
|
@@ -351,6 +372,7 @@ function terminalReplayPayloadForMode(input: {
|
|
|
351
372
|
let totalCount = entries.length;
|
|
352
373
|
if (
|
|
353
374
|
input.durableLogMode === 'direct_worker' &&
|
|
375
|
+
!input.resultLogIds &&
|
|
354
376
|
!input.replayAllTerminalLogs
|
|
355
377
|
) {
|
|
356
378
|
const observedCounts = new Map<string, number>();
|
|
@@ -377,9 +399,14 @@ function terminalReplayPayloadForMode(input: {
|
|
|
377
399
|
}
|
|
378
400
|
const lines = entries.map((entry) => entry.line);
|
|
379
401
|
const lineContexts = entries.map((entry) => entry.context);
|
|
402
|
+
const lineMetadata = entries.map((entry) => entry.metadata);
|
|
380
403
|
return {
|
|
381
404
|
lines,
|
|
382
405
|
...(lineContexts.some(Boolean) ? { lineContexts } : {}),
|
|
406
|
+
...(lineMetadata.some(Boolean) ? { lineMetadata } : {}),
|
|
407
|
+
...(input.resultLogIds
|
|
408
|
+
? { lineIds: entries.map((entry) => entry.entryId) }
|
|
409
|
+
: {}),
|
|
383
410
|
...(input.replayAllTerminalLogs ? { dedupeMode: 'exact' as const } : {}),
|
|
384
411
|
...(typeof input.producerAttempt === 'number'
|
|
385
412
|
? { producerAttempt: input.producerAttempt }
|
|
@@ -439,6 +466,8 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
439
466
|
let nodeProgressSealed = false;
|
|
440
467
|
let liveLogs: string[] = [];
|
|
441
468
|
let liveLogContexts: Array<PlayLogContext | null> = [];
|
|
469
|
+
let liveLogMetadata: Array<PlayLogMetadata | null> = [];
|
|
470
|
+
let liveLogIds: Array<string | null> = [];
|
|
442
471
|
let liveLogsTotalCount = input.initialLiveLogTotalCount ?? 0;
|
|
443
472
|
let liveLogsDirty = false;
|
|
444
473
|
let workCounters: RuntimeStatusUpdate['workCounters'];
|
|
@@ -479,6 +508,9 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
479
508
|
|
|
480
509
|
let activeProgressUpload: Promise<void> | null = null;
|
|
481
510
|
let pendingProgressUpload: ProgressFlushRequest | null = null;
|
|
511
|
+
let deferredStepFailureFlushTimer: ReturnType<typeof setTimeout> | null =
|
|
512
|
+
null;
|
|
513
|
+
let terminalFailureProgressPersisted = false;
|
|
482
514
|
|
|
483
515
|
const mergeProgressFlushRequest = (
|
|
484
516
|
current: ProgressFlushRequest | null,
|
|
@@ -541,6 +573,8 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
541
573
|
: undefined;
|
|
542
574
|
const nextLogs = liveLogs.slice();
|
|
543
575
|
const nextLogContexts = liveLogContexts.slice();
|
|
576
|
+
const nextLogMetadata = liveLogMetadata.slice();
|
|
577
|
+
const nextLogIds = liveLogIds.slice();
|
|
544
578
|
const nextLogTotalCount = liveLogsTotalCount;
|
|
545
579
|
const nextDatasetLifecycleEvents = includeProgress
|
|
546
580
|
? pendingDatasetLifecycleEvents
|
|
@@ -563,10 +597,47 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
563
597
|
await publishStatusUpdate({
|
|
564
598
|
playId: launch.runId,
|
|
565
599
|
status: request.status,
|
|
566
|
-
|
|
600
|
+
// On failure only observed causal errors may precede the
|
|
601
|
+
// terminal. Partial map counters cannot prove completion.
|
|
602
|
+
liveNodeProgress: nodeProgressSealed
|
|
603
|
+
? terminalFailureProgressPersisted
|
|
604
|
+
? {}
|
|
605
|
+
: Object.fromEntries(
|
|
606
|
+
Object.entries(nextProgress ?? {})
|
|
607
|
+
.filter(
|
|
608
|
+
([, progress]) =>
|
|
609
|
+
progress.error || progress.nodeIo?.error,
|
|
610
|
+
)
|
|
611
|
+
.map(([id, progress]) => [
|
|
612
|
+
id,
|
|
613
|
+
{
|
|
614
|
+
startedAt: progress.startedAt,
|
|
615
|
+
completedAt: progress.completedAt,
|
|
616
|
+
updatedAt: progress.updatedAt,
|
|
617
|
+
error:
|
|
618
|
+
progress.error ??
|
|
619
|
+
progress.nodeIo?.error ??
|
|
620
|
+
undefined,
|
|
621
|
+
nodeIo: progress.nodeIo,
|
|
622
|
+
},
|
|
623
|
+
]),
|
|
624
|
+
)
|
|
625
|
+
: terminalFailureProgressPersisted
|
|
626
|
+
? Object.fromEntries(
|
|
627
|
+
Object.entries(nextProgress ?? {}).filter(
|
|
628
|
+
([, progress]) =>
|
|
629
|
+
!progress.error && !progress.nodeIo?.error,
|
|
630
|
+
),
|
|
631
|
+
)
|
|
632
|
+
: nextProgress,
|
|
567
633
|
liveLogs: nextLogs,
|
|
568
634
|
liveLogContexts: nextLogContexts,
|
|
635
|
+
liveLogMetadata: nextLogMetadata,
|
|
636
|
+
liveLogIds: nextLogIds,
|
|
569
637
|
liveLogTotalCount: nextLogTotalCount,
|
|
638
|
+
...(input.initialLiveLogTotalCount !== undefined
|
|
639
|
+
? { liveLogBatchOffset: nextLogTotalCount - nextLogs.length }
|
|
640
|
+
: {}),
|
|
570
641
|
liveLogProducerAttempt: producerAttempt,
|
|
571
642
|
datasetLifecycleEvents: nextDatasetLifecycleEvents,
|
|
572
643
|
activityObservations: nextActivityObservations,
|
|
@@ -667,7 +738,12 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
667
738
|
void flush({ force }).catch(() => undefined);
|
|
668
739
|
};
|
|
669
740
|
|
|
670
|
-
const appendLiveLog = (
|
|
741
|
+
const appendLiveLog = (
|
|
742
|
+
line: string,
|
|
743
|
+
context?: unknown,
|
|
744
|
+
entryId?: unknown,
|
|
745
|
+
metadata?: unknown,
|
|
746
|
+
) => {
|
|
671
747
|
const trimmed = line.trim();
|
|
672
748
|
if (!trimmed) {
|
|
673
749
|
return;
|
|
@@ -675,8 +751,12 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
675
751
|
liveLogsTotalCount += 1;
|
|
676
752
|
liveLogs = [...liveLogs, trimmed];
|
|
677
753
|
liveLogContexts = [...liveLogContexts, normalizePlayLogContext(context)];
|
|
754
|
+
liveLogMetadata = [...liveLogMetadata, normalizePlayLogMetadata(metadata)];
|
|
755
|
+
liveLogIds = [...liveLogIds, typeof entryId === 'string' ? entryId : null];
|
|
678
756
|
if (liveLogs.length > LIVE_LOG_TAIL_LIMIT) {
|
|
679
757
|
liveLogs = liveLogs.slice(liveLogs.length - LIVE_LOG_TAIL_LIMIT);
|
|
758
|
+
liveLogMetadata = liveLogMetadata.slice(-LIVE_LOG_TAIL_LIMIT);
|
|
759
|
+
liveLogIds = liveLogIds.slice(liveLogIds.length - LIVE_LOG_TAIL_LIMIT);
|
|
680
760
|
liveLogContexts = liveLogContexts.slice(
|
|
681
761
|
liveLogContexts.length - LIVE_LOG_TAIL_LIMIT,
|
|
682
762
|
);
|
|
@@ -742,6 +822,7 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
742
822
|
...(current.completedAt !== undefined && !clearCompletedAt
|
|
743
823
|
? { completedAt: current.completedAt }
|
|
744
824
|
: {}),
|
|
825
|
+
...(current.error !== undefined ? { error: current.error } : {}),
|
|
745
826
|
...(current.nodeIo !== undefined ? { nodeIo: current.nodeIo } : {}),
|
|
746
827
|
...(current.conditionCounts !== undefined
|
|
747
828
|
? { conditionCounts: current.conditionCounts }
|
|
@@ -791,6 +872,7 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
791
872
|
...(patch.completedAt !== undefined
|
|
792
873
|
? { completedAt: patch.completedAt }
|
|
793
874
|
: {}),
|
|
875
|
+
...(patch.error !== undefined ? { error: patch.error } : {}),
|
|
794
876
|
...(patch.nodeIo !== undefined ? { nodeIo: patch.nodeIo } : {}),
|
|
795
877
|
...(patch.conditionCounts !== undefined
|
|
796
878
|
? { conditionCounts: patch.conditionCounts }
|
|
@@ -975,6 +1057,34 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
975
1057
|
};
|
|
976
1058
|
|
|
977
1059
|
const applyExecutionEvent = (event: PlayExecutionEvent) => {
|
|
1060
|
+
if (
|
|
1061
|
+
event.type === 'step.started' ||
|
|
1062
|
+
event.type === 'step.completed' ||
|
|
1063
|
+
event.type === 'step.failed'
|
|
1064
|
+
) {
|
|
1065
|
+
setNodeProgress(event.stepId, {
|
|
1066
|
+
...(event.type === 'step.started'
|
|
1067
|
+
? { startedAt: event.at }
|
|
1068
|
+
: { completedAt: event.at }),
|
|
1069
|
+
...(event.type === 'step.failed' ? { error: event.error } : {}),
|
|
1070
|
+
});
|
|
1071
|
+
if (event.type === 'step.failed') {
|
|
1072
|
+
// A failing step is often followed immediately by run.failed. Give
|
|
1073
|
+
// that terminal transaction a chance to carry the error in its own
|
|
1074
|
+
// ordered outbox row, avoiding a redundant Convex append ahead of it.
|
|
1075
|
+
if (deferredStepFailureFlushTimer !== null) {
|
|
1076
|
+
clearTimeout(deferredStepFailureFlushTimer);
|
|
1077
|
+
}
|
|
1078
|
+
deferredStepFailureFlushTimer = setTimeout(() => {
|
|
1079
|
+
deferredStepFailureFlushTimer = null;
|
|
1080
|
+
if (!terminalFailureProgressPersisted) scheduleFlush(true);
|
|
1081
|
+
}, STEP_FAILURE_PROGRESS_DEBOUNCE_MS);
|
|
1082
|
+
deferredStepFailureFlushTimer.unref?.();
|
|
1083
|
+
} else {
|
|
1084
|
+
scheduleFlush(true);
|
|
1085
|
+
}
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
978
1088
|
if (event.type === 'docflow.node.hit') {
|
|
979
1089
|
setNodeProgress(event.nodeId, {
|
|
980
1090
|
startedAt: event.at,
|
|
@@ -1321,13 +1431,20 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
1321
1431
|
line?: unknown;
|
|
1322
1432
|
source?: unknown;
|
|
1323
1433
|
context?: unknown;
|
|
1434
|
+
metadata?: unknown;
|
|
1435
|
+
entryId?: unknown;
|
|
1324
1436
|
};
|
|
1325
1437
|
const rawLine = logEvent.line;
|
|
1326
1438
|
if (typeof rawLine !== 'string' || !rawLine) {
|
|
1327
1439
|
return;
|
|
1328
1440
|
}
|
|
1329
1441
|
if (shouldPublishDurableLogEvent(durableLogMode, logEvent)) {
|
|
1330
|
-
appendLiveLog(
|
|
1442
|
+
appendLiveLog(
|
|
1443
|
+
rawLine,
|
|
1444
|
+
logEvent.context,
|
|
1445
|
+
logEvent.entryId,
|
|
1446
|
+
logEvent.metadata,
|
|
1447
|
+
);
|
|
1331
1448
|
}
|
|
1332
1449
|
markMapStartedFromLog(rawLine);
|
|
1333
1450
|
markMapStartedFromPerfLog(rawLine);
|
|
@@ -1593,13 +1710,14 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
1593
1710
|
// A failed result's terminal is already decided: every caller that
|
|
1594
1711
|
// finalizes one does so after (or while) the scheduler commits
|
|
1595
1712
|
// `run.failed`, and the ledger freezes a terminal run's steps. Its step
|
|
1596
|
-
//
|
|
1713
|
+
// counts from here are projections of this reporter's own partial view — a
|
|
1597
1714
|
// parked worker's fold saw page-sized map totals and never saw the
|
|
1598
1715
|
// runner's failures — so they used to land after `run.failed` claiming
|
|
1599
1716
|
// docflow nodes were "running" and a 50,000-row map had "completed" 2,000.
|
|
1600
1717
|
// The settled counts that ARE true travel in the terminal outbox row
|
|
1601
1718
|
// (`buildFailedTerminalSettledProgressEvents`). Logs, work counters,
|
|
1602
|
-
// dataset and activity
|
|
1719
|
+
// dataset and activity still flush; causal step failures already ride the
|
|
1720
|
+
// terminal outbox row when one was committed.
|
|
1603
1721
|
if (result?.status === 'failed') {
|
|
1604
1722
|
nodeProgressSealed = true;
|
|
1605
1723
|
} else if (result?.status === 'completed') {
|
|
@@ -1629,6 +1747,8 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
1629
1747
|
: terminalReplayPayloadForMode({
|
|
1630
1748
|
resultLogs: result.logs,
|
|
1631
1749
|
resultLogContexts: result.logContexts,
|
|
1750
|
+
resultLogMetadata: result.logMetadata,
|
|
1751
|
+
resultLogIds: result.logIds,
|
|
1632
1752
|
durableLogMode,
|
|
1633
1753
|
liveLogs,
|
|
1634
1754
|
liveLogContexts,
|
|
@@ -1640,11 +1760,47 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
1640
1760
|
});
|
|
1641
1761
|
};
|
|
1642
1762
|
|
|
1763
|
+
const collectTerminalFailureProgressEvents = (): PlayRunLedgerEvent[] => {
|
|
1764
|
+
const failedProgress = Object.fromEntries(
|
|
1765
|
+
Object.entries(liveNodeProgress).filter(
|
|
1766
|
+
([, progress]) => progress.error || progress.nodeIo?.error,
|
|
1767
|
+
),
|
|
1768
|
+
);
|
|
1769
|
+
if (Object.keys(failedProgress).length === 0) return [];
|
|
1770
|
+
return buildPlayRunLedgerEventsFromStatusPatch({
|
|
1771
|
+
patch: {
|
|
1772
|
+
runId: launch.runId,
|
|
1773
|
+
status: 'running',
|
|
1774
|
+
lastCheckpointAt: Date.now(),
|
|
1775
|
+
liveNodeProgress: failedProgress,
|
|
1776
|
+
},
|
|
1777
|
+
previousSnapshot: createEmptyPlayRunLedgerSnapshot({
|
|
1778
|
+
runId: launch.runId,
|
|
1779
|
+
status: 'queued',
|
|
1780
|
+
}),
|
|
1781
|
+
source: 'worker',
|
|
1782
|
+
}).filter(
|
|
1783
|
+
(event) =>
|
|
1784
|
+
event.type === RUN_EVENT_KIND.stepProgress ||
|
|
1785
|
+
event.type === RUN_EVENT_KIND.stepFailed,
|
|
1786
|
+
);
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
const markTerminalFailureProgressPersisted = (): void => {
|
|
1790
|
+
terminalFailureProgressPersisted = true;
|
|
1791
|
+
if (deferredStepFailureFlushTimer !== null) {
|
|
1792
|
+
clearTimeout(deferredStepFailureFlushTimer);
|
|
1793
|
+
deferredStepFailureFlushTimer = null;
|
|
1794
|
+
}
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1643
1797
|
return {
|
|
1644
1798
|
onRunnerEvent,
|
|
1645
1799
|
applyDurableObservations,
|
|
1646
1800
|
applyTerminalSheetOutcomes,
|
|
1647
1801
|
finalize,
|
|
1802
|
+
collectTerminalFailureProgressEvents,
|
|
1803
|
+
markTerminalFailureProgressPersisted,
|
|
1648
1804
|
};
|
|
1649
1805
|
}
|
|
1650
1806
|
|
|
@@ -1832,6 +1988,8 @@ export async function collectGatewayRunnerTerminalProgressLedgerEvents(input: {
|
|
|
1832
1988
|
liveNodeProgress: finalUpdate.liveNodeProgress,
|
|
1833
1989
|
liveLogs,
|
|
1834
1990
|
liveLogContexts: finalUpdate.liveLogContexts,
|
|
1991
|
+
liveLogMetadata: finalUpdate.liveLogMetadata,
|
|
1992
|
+
liveLogIds: finalUpdate.liveLogIds,
|
|
1835
1993
|
liveLogsChannelOffset: Math.max(0, liveLogTotalCount - liveLogs.length),
|
|
1836
1994
|
liveLogsProducerAttempt:
|
|
1837
1995
|
finalUpdate.liveLogProducerAttempt ?? input.attempt,
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
import {
|
|
11
11
|
DEFAULT_ADVISORY_PROVIDER_MAX_RPS,
|
|
12
12
|
DEFAULT_UNKNOWN_PROVIDER_COLD_START_RPS,
|
|
13
|
+
DEFAULT_UNKNOWN_PROVIDER_MIN_RPS,
|
|
13
14
|
PROVIDER_AIMD_COOLDOWN_CAP_MS,
|
|
14
15
|
type PacingRule,
|
|
15
16
|
} from '../governor/rate-state-backend';
|
|
@@ -431,7 +432,8 @@ function normalizeRules(
|
|
|
431
432
|
* per second; `capacity` is one window of burst. The adaptive `[min,max]` range
|
|
432
433
|
* is derived value-first so the same wire shape drives both declared and
|
|
433
434
|
* default-pacing buckets:
|
|
434
|
-
* - min_rps =
|
|
435
|
+
* - min_rps = 2 for the unknown shared-provider scope
|
|
436
|
+
* - min_rps = min(5/60, max) for declared rules
|
|
435
437
|
* (a lower explicit adaptive maximum remains authoritative)
|
|
436
438
|
* - max_rps = max(base, 100) (catalogued-rate recommendation)
|
|
437
439
|
* - max_rps = explicit adaptiveMaxRps when supplied
|
|
@@ -454,11 +456,11 @@ function seedForRule(rule: AcquirePacingRule): RateBucketSeed {
|
|
|
454
456
|
rule.adaptiveMaxRps != null && rule.adaptiveMaxRps > 0
|
|
455
457
|
? Math.max(rule.adaptiveMaxRps, base)
|
|
456
458
|
: Math.max(base, DEFAULT_ADVISORY_PROVIDER_MAX_RPS);
|
|
457
|
-
//
|
|
458
|
-
// scopes
|
|
459
|
+
// The unknown-provider seed is a guess, not a quota floor. Declared provider
|
|
460
|
+
// scopes keep the five-RPM floor, capped by an explicit adaptive maximum.
|
|
459
461
|
const minRps =
|
|
460
462
|
rule.ruleId === 'default:provider-scope'
|
|
461
|
-
?
|
|
463
|
+
? DEFAULT_UNKNOWN_PROVIDER_MIN_RPS
|
|
462
464
|
: Math.min(MIN_ADAPTIVE_RPS_FLOOR, maxRps);
|
|
463
465
|
return {
|
|
464
466
|
rule_id: rule.ruleId,
|
|
@@ -472,7 +474,7 @@ function seedForRule(rule: AcquirePacingRule): RateBucketSeed {
|
|
|
472
474
|
|
|
473
475
|
function minimumRateRpsForRules(rules: readonly AcquirePacingRule[]): number {
|
|
474
476
|
if (rules.some((rule) => rule.ruleId === 'default:provider-scope')) {
|
|
475
|
-
return
|
|
477
|
+
return DEFAULT_UNKNOWN_PROVIDER_MIN_RPS;
|
|
476
478
|
}
|
|
477
479
|
const explicitAdaptiveMaxima = rules.flatMap((rule) => {
|
|
478
480
|
if (rule.adaptiveMaxRps == null || rule.adaptiveMaxRps <= 0) return [];
|