deepline 0.3.89 → 0.3.90
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/contracts/install-commands.json +29 -0
- package/dist/bundling-sources/sdk/src/client.ts +120 -37
- package/dist/bundling-sources/sdk/src/index.ts +4 -0
- package/dist/bundling-sources/sdk/src/pagination.ts +25 -0
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
- package/dist/bundling-sources/sdk/src/release.ts +16 -9
- package/dist/bundling-sources/sdk/src/types.ts +23 -0
- package/dist/bundling-sources/shared_libs/monitors/analytics-public.ts +10 -0
- package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +3 -0
- package/dist/bundling-sources/shared_libs/monitors/inbound-queue.ts +4 -4
- package/dist/bundling-sources/shared_libs/monitors/inventory-reconciliation.ts +59 -0
- package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +7 -9
- package/dist/bundling-sources/shared_libs/monitors/package.json +7 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +44 -2
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-consumer-health.ts +2 -2
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +2 -2
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-dead-letter.ts +245 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-message.ts +38 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-ack-probe.ts +94 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-delivery.ts +60 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue.ts +60 -0
- package/dist/bundling-sources/shared_libs/monitors/snitcher-install-validation.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/snitcher-ip2company.ts +2 -2
- package/dist/bundling-sources/shared_libs/monitors/snitcher-radar.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/tracking-provider-integrations.ts +1 -1
- package/dist/bundling-sources/shared_libs/observability/build-context.ts +13 -0
- package/dist/bundling-sources/shared_libs/observability/log-metadata.ts +160 -0
- package/dist/bundling-sources/shared_libs/observability/request-context.ts +68 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +654 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +35 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-work.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/neon-constants.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +272 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +45 -0
- package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +66 -1
- package/dist/bundling-sources/shared_libs/play-runtime/async-play-invocation.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/bounceban-batching.ts +137 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +779 -140
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +46 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-payload-contract.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +725 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +29 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +23 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-invocation-fence.ts +442 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-protocol.ts +3 -3
- package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +18 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +66 -0
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +145 -0
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/ports.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/postgres-store.ts +170 -0
- package/dist/bundling-sources/shared_libs/play-runtime/internal-bootstrap-token.ts +1322 -0
- package/dist/bundling-sources/shared_libs/play-runtime/internal-step-ids.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/lane-retirement-gate.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +112 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +8 -0
- package/dist/bundling-sources/shared_libs/play-runtime/perf-trace.ts +127 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-artifact-runtime-contract.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +24 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/contracts.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/index.ts +13 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/job.ts +41 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/ports.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/postgres-adapter.ts +54 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/contracts.ts +114 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/engine-suspension.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/index.ts +36 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/job.ts +97 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/ports.ts +104 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/postgres-fence-adapter.ts +263 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/worker-terminal-projection.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/contracts.ts +29 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/index.ts +15 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/job.ts +65 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/ports.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/contracts.ts +42 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/index.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/job.ts +142 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/ports.ts +42 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/postgres-adapter.ts +102 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +60 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/index.ts +16 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +78 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +54 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +71 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +5 -8
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +3 -5
- package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +168 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +72 -9
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +6 -12
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +19 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +14 -44
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +57 -205
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-authority.ts +201 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-env.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +67 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-release-environment.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-contract.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +14 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-telemetry.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-deadline-reconciliation.ts +88 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +124 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-run-task.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-pool.ts +193 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-lifecycle.ts +269 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-suffix.ts +73 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +6642 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +897 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-metrics.ts +120 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +76 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +476 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +147 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/index.ts +27 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/lazy.ts +50 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/play-run-recovery.ts +98 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-budget-state.ts +260 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +1416 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +1255 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +942 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +14636 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +888 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +355 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +46 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runner-push.ts +60 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-api-origin.ts +33 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-gateway-health.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +835 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-ledger-reconciliation.ts +1218 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-operation.ts +207 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-scheduler-schema.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-capacity.ts +123 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-launch-read-observability.ts +78 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/submit-admission.ts +169 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/terminal-contract.ts +20 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-queue-metrics.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +249 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/step-progress.ts +296 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +3 -3
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +20 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +60 -15
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execution-outcome.ts +20 -0
- package/dist/bundling-sources/shared_libs/play-runtime/transient-service-error.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +14 -1
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-storage-contract.ts +165 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +15 -23
- package/dist/bundling-sources/shared_libs/play-runtime/workflow-executor-auth.ts +169 -0
- package/dist/bundling-sources/shared_libs/play-runtime/zerobounce-batching.ts +122 -0
- package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/deployment-status.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/docflow.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/play-grep-search.ts +39 -0
- package/dist/bundling-sources/shared_libs/plays/share/copy.ts +271 -0
- package/dist/bundling-sources/shared_libs/plays/share/metrics.ts +140 -0
- package/dist/bundling-sources/shared_libs/plays/share/public-read-model.ts +86 -0
- package/dist/bundling-sources/shared_libs/plays/share/seo-quality.ts +181 -0
- package/dist/bundling-sources/shared_libs/plays/share/static-pipeline-summary.ts +658 -0
- package/dist/bundling-sources/shared_libs/plays/share/types.ts +197 -0
- package/dist/bundling-sources/shared_libs/plays/slack-hitl-timeout-error.ts +37 -0
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +6 -1
- package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +1 -1
- package/dist/bundling-sources/shared_libs/product-notifications/configuration.ts +7 -1
- package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +3 -2
- package/dist/bundling-sources/shared_libs/product-notifications/overflow.ts +18 -0
- package/dist/bundling-sources/shared_libs/product-notifications/rolling-limit.ts +24 -0
- package/dist/bundling-sources/shared_libs/product-notifications/slack-delivery.ts +624 -0
- package/dist/bundling-sources/shared_libs/product-notifications/slack-payload.ts +140 -0
- package/dist/bundling-sources/shared_libs/product-notifications/slack-transport.ts +75 -0
- package/dist/bundling-sources/shared_libs/runtime-env.ts +6 -0
- package/dist/bundling-sources/shared_libs/security/email-blacklist.ts +42 -0
- package/dist/bundling-sources/shared_libs/security/header-safe-env.ts +10 -3
- package/dist/bundling-sources/shared_libs/security/slack-sanitize.ts +133 -0
- package/dist/cli/getting-started.d.mts +52 -0
- package/dist/cli/getting-started.d.ts +52 -0
- package/dist/cli/getting-started.js +65 -0
- package/dist/cli/getting-started.mjs +36 -0
- package/dist/cli/index.d.mts +9 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +545 -264
- package/dist/cli/index.mjs +543 -271
- package/dist/{compiler-manifest-DsLbQaew.d.mts → compiler-manifest-BaJfE-5H.d.mts} +5 -3
- package/dist/{compiler-manifest-DsLbQaew.d.ts → compiler-manifest-BaJfE-5H.d.ts} +5 -3
- package/dist/index.d.mts +81 -148
- package/dist/index.d.ts +81 -148
- package/dist/index.js +83 -26
- package/dist/index.mjs +83 -26
- package/dist/install-integrity.json +130 -2
- package/dist/monitor-fleet-contract.d.mts +138 -0
- package/dist/monitor-fleet-contract.d.ts +138 -0
- package/dist/monitor-fleet-contract.js +404 -0
- package/dist/monitor-fleet-contract.mjs +367 -0
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/release.d.mts +255 -0
- package/dist/release.d.ts +255 -0
- package/dist/release.js +215 -0
- package/dist/release.mjs +188 -0
- package/package.json +21 -4
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps receipt-completion bursts from occupying every local database client.
|
|
3
|
+
* Heartbeats and initial runner readiness deliberately do not use this gate.
|
|
4
|
+
*/
|
|
5
|
+
export class GatewayHeavyRequestAdmission {
|
|
6
|
+
readonly #waiting: Array<() => void> = [];
|
|
7
|
+
#active = 0;
|
|
8
|
+
|
|
9
|
+
constructor(readonly maxActive: number) {
|
|
10
|
+
if (!Number.isInteger(maxActive) || maxActive < 1) {
|
|
11
|
+
throw new Error('Gateway heavy-request admission requires maxActive >= 1.');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async acquire(signal?: AbortSignal): Promise<() => void> {
|
|
16
|
+
if (signal?.aborted) throw new Error('Gateway request admission was cancelled.');
|
|
17
|
+
if (this.#active < this.maxActive) return this.#grant();
|
|
18
|
+
return await new Promise<() => void>((resolve, reject) => {
|
|
19
|
+
const grant = () => {
|
|
20
|
+
signal?.removeEventListener('abort', abort);
|
|
21
|
+
resolve(this.#grant());
|
|
22
|
+
};
|
|
23
|
+
const abort = () => {
|
|
24
|
+
const index = this.#waiting.indexOf(grant);
|
|
25
|
+
if (index >= 0) this.#waiting.splice(index, 1);
|
|
26
|
+
reject(new Error('Gateway request admission was cancelled.'));
|
|
27
|
+
};
|
|
28
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
29
|
+
this.#waiting.push(grant);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
snapshot(): { active: number; waiting: number } {
|
|
34
|
+
return { active: this.#active, waiting: this.#waiting.length };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#grant(): () => void {
|
|
38
|
+
this.#active += 1;
|
|
39
|
+
let released = false;
|
|
40
|
+
return () => {
|
|
41
|
+
if (released) return;
|
|
42
|
+
released = true;
|
|
43
|
+
this.#active -= 1;
|
|
44
|
+
this.#waiting.shift()?.();
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { PlaySchedulerSubmitInput } from '../scheduler-backend';
|
|
2
|
+
import type { PlayRunnerResult } from '../protocol';
|
|
3
|
+
import {
|
|
4
|
+
createDirectWorkerProgressReporter,
|
|
5
|
+
type PostgresSchedulerProgressReporter,
|
|
6
|
+
} from './postgres-progress';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Stateless gateway fold for runner progress.
|
|
10
|
+
*
|
|
11
|
+
* HORIZONTAL-SCALING INVARIANT: every request creates a fresh reporter and is
|
|
12
|
+
* acknowledged only after its events reach the durable app-runtime projection.
|
|
13
|
+
* The runner supplies the absolute durable-log offset, so Fly may spread every
|
|
14
|
+
* consecutive batch and the terminal across different Machines without sticky
|
|
15
|
+
* routing, shared memory, or a replica-affine cursor. Attempt fencing belongs to
|
|
16
|
+
* the durable Play Run Ledger, never to a gateway process.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface GatewayProgressRegistry {
|
|
20
|
+
ingest: (input: {
|
|
21
|
+
launch: PlaySchedulerSubmitInput;
|
|
22
|
+
runId: string;
|
|
23
|
+
attempt: number;
|
|
24
|
+
events: unknown[];
|
|
25
|
+
liveLogOffset: number;
|
|
26
|
+
isCurrentAttempt: () => Promise<boolean>;
|
|
27
|
+
}) => Promise<void>;
|
|
28
|
+
/** Flush + drop a run's reporter (e.g. on the run's terminal). */
|
|
29
|
+
finalize: (runId: string, attempt: number) => Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Finalize a pushed detached runner when the gateway, rather than a wake
|
|
32
|
+
* leg, writes the terminal. Creates a reporter when no progress batch was
|
|
33
|
+
* received so terminal step state is still projected.
|
|
34
|
+
*/
|
|
35
|
+
finalizeTerminal: (input: {
|
|
36
|
+
launch: PlaySchedulerSubmitInput;
|
|
37
|
+
runId: string;
|
|
38
|
+
attempt: number;
|
|
39
|
+
result: PlayRunnerResult;
|
|
40
|
+
liveLogOffset: number;
|
|
41
|
+
isCurrentAttempt: () => Promise<boolean>;
|
|
42
|
+
replayTerminalLogs?: boolean;
|
|
43
|
+
events?: unknown[];
|
|
44
|
+
}) => Promise<void>;
|
|
45
|
+
size: () => number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Project a runner's final observation tail before its terminal transition.
|
|
50
|
+
* Suspended and failed attempts need the same ordering as completed attempts:
|
|
51
|
+
* either terminal can wake/permit a successor whose attempt fence would make
|
|
52
|
+
* a late predecessor progress upload inert.
|
|
53
|
+
*/
|
|
54
|
+
export async function projectGatewayRunnerTerminalProgress(input: {
|
|
55
|
+
registry: GatewayProgressRegistry;
|
|
56
|
+
launch: PlaySchedulerSubmitInput;
|
|
57
|
+
runId: string;
|
|
58
|
+
attempt: number;
|
|
59
|
+
result: PlayRunnerResult;
|
|
60
|
+
events: unknown[];
|
|
61
|
+
liveLogOffset: number;
|
|
62
|
+
isCurrentAttempt: () => Promise<boolean>;
|
|
63
|
+
replayTerminalLogs?: boolean;
|
|
64
|
+
}): Promise<void> {
|
|
65
|
+
if (input.result.status === 'completed') {
|
|
66
|
+
// Fold the observation tail and terminal step state in one reporter. Two
|
|
67
|
+
// separate reporters require two fenced app uploads on the critical path.
|
|
68
|
+
// The reporter owns cursor advancement and acknowledges the whole fold
|
|
69
|
+
// before the caller can commit the terminal fact.
|
|
70
|
+
await input.registry.finalizeTerminal({
|
|
71
|
+
launch: input.launch,
|
|
72
|
+
runId: input.runId,
|
|
73
|
+
attempt: input.attempt,
|
|
74
|
+
result: input.result,
|
|
75
|
+
events: input.events,
|
|
76
|
+
liveLogOffset: input.liveLogOffset,
|
|
77
|
+
isCurrentAttempt: input.isCurrentAttempt,
|
|
78
|
+
replayTerminalLogs: input.replayTerminalLogs,
|
|
79
|
+
});
|
|
80
|
+
} else if (input.events.length > 0) {
|
|
81
|
+
await input.registry.ingest({
|
|
82
|
+
launch: input.launch,
|
|
83
|
+
runId: input.runId,
|
|
84
|
+
attempt: input.attempt,
|
|
85
|
+
events: input.events,
|
|
86
|
+
liveLogOffset: input.liveLogOffset,
|
|
87
|
+
isCurrentAttempt: input.isCurrentAttempt,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function createGatewayProgressRegistry(options?: {
|
|
93
|
+
createReporter?: (
|
|
94
|
+
launch: PlaySchedulerSubmitInput,
|
|
95
|
+
attempt: number,
|
|
96
|
+
isCurrentAttempt: () => boolean | Promise<boolean>,
|
|
97
|
+
initialLiveLogTotalCount: number,
|
|
98
|
+
) => PostgresSchedulerProgressReporter;
|
|
99
|
+
}): GatewayProgressRegistry {
|
|
100
|
+
const createReporter =
|
|
101
|
+
options?.createReporter ??
|
|
102
|
+
((launch, attempt, isCurrentAttempt, initialLiveLogTotalCount) =>
|
|
103
|
+
createDirectWorkerProgressReporter({
|
|
104
|
+
launch,
|
|
105
|
+
producerAttempt: attempt,
|
|
106
|
+
isCurrentAttempt,
|
|
107
|
+
initialLiveLogTotalCount,
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
async ingest({ launch, attempt, events, liveLogOffset, isCurrentAttempt }) {
|
|
112
|
+
const reporter = createReporter(
|
|
113
|
+
launch,
|
|
114
|
+
attempt,
|
|
115
|
+
isCurrentAttempt,
|
|
116
|
+
liveLogOffset,
|
|
117
|
+
);
|
|
118
|
+
for (const event of events) {
|
|
119
|
+
reporter.onRunnerEvent(event);
|
|
120
|
+
}
|
|
121
|
+
await reporter.finalize(null, {
|
|
122
|
+
status: 'running',
|
|
123
|
+
required: true,
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
async finalize() {},
|
|
127
|
+
async finalizeTerminal(input) {
|
|
128
|
+
const reporter = createReporter(
|
|
129
|
+
input.launch,
|
|
130
|
+
input.attempt,
|
|
131
|
+
input.isCurrentAttempt,
|
|
132
|
+
input.liveLogOffset,
|
|
133
|
+
);
|
|
134
|
+
for (const event of input.events ?? []) reporter.onRunnerEvent(event);
|
|
135
|
+
await reporter.finalize(input.result, {
|
|
136
|
+
status: 'running',
|
|
137
|
+
required: true,
|
|
138
|
+
skipTerminalLogReplay: input.replayTerminalLogs !== true,
|
|
139
|
+
replayAllTerminalLogs: input.replayTerminalLogs === true,
|
|
140
|
+
terminalLogProducerAttempt: input.attempt,
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
size() {
|
|
144
|
+
return 0;
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolver for Play Scheduler backends — the Adapters that own play workflow
|
|
3
|
+
* lifecycle (submit, attach, observe, signal, cancel, result).
|
|
4
|
+
*
|
|
5
|
+
* Absurd is the only durable scheduler.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
normalizePlaySchedulerBackend,
|
|
10
|
+
PLAY_SCHEDULER_BACKENDS,
|
|
11
|
+
type PlaySchedulerBackend,
|
|
12
|
+
type PlaySchedulerBackendId,
|
|
13
|
+
} from '../scheduler-backend';
|
|
14
|
+
import { absurdSchedulerBackend } from './absurd';
|
|
15
|
+
|
|
16
|
+
export function resolveSchedulerBackend(
|
|
17
|
+
backendId?: PlaySchedulerBackendId | string | null,
|
|
18
|
+
): PlaySchedulerBackend {
|
|
19
|
+
const backend = normalizePlaySchedulerBackend(backendId);
|
|
20
|
+
if (backend === PLAY_SCHEDULER_BACKENDS.absurd) {
|
|
21
|
+
return absurdSchedulerBackend;
|
|
22
|
+
}
|
|
23
|
+
throw new Error(`Unsupported scheduler backend: ${backend}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { absurdSchedulerBackend };
|
|
27
|
+
export type { PlaySchedulerBackend };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizePlaySchedulerBackend,
|
|
3
|
+
PLAY_SCHEDULER_BACKENDS,
|
|
4
|
+
type PlaySchedulerBackend,
|
|
5
|
+
type PlaySchedulerBackendId,
|
|
6
|
+
} from '../scheduler-backend';
|
|
7
|
+
import { resolveAbsurdQueueSuffixForSchema } from '../scheduler-backends/absurd-shared';
|
|
8
|
+
|
|
9
|
+
export type SchedulerBackendResolveOptions = {
|
|
10
|
+
postgresUrl?: string | null;
|
|
11
|
+
postgresSchema?: string | null;
|
|
12
|
+
resolveQueueSuffixForSchema?: (schema: string) => string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function resolveSchedulerBackendLazy(
|
|
16
|
+
backendId?: PlaySchedulerBackendId | string | null,
|
|
17
|
+
options: SchedulerBackendResolveOptions = {},
|
|
18
|
+
): Promise<PlaySchedulerBackend> {
|
|
19
|
+
const backend = normalizePlaySchedulerBackend(backendId);
|
|
20
|
+
if (backend === PLAY_SCHEDULER_BACKENDS.absurd) {
|
|
21
|
+
const { createAbsurdSchedulerBackend, absurdSchedulerBackend } =
|
|
22
|
+
await import('./absurd');
|
|
23
|
+
const schema = options.postgresSchema?.trim();
|
|
24
|
+
if (schema) {
|
|
25
|
+
// Resolve the queue suffix HERE (the request path) rather than deriving it
|
|
26
|
+
// inside the backend: the shared hosted app has no
|
|
27
|
+
// DEEPLINE_ABSURD_QUEUE_SUFFIX env, so it must derive the SAME suffix the
|
|
28
|
+
// Fly worker was provisioned with from the per-request schema, to enqueue
|
|
29
|
+
// onto the queues that worker actually drains. For a preview namespace
|
|
30
|
+
// that identity includes the sandbox placement policy, so this must go
|
|
31
|
+
// through resolveAbsurdQueueSuffixForRuntimeSchema and not hash the bare
|
|
32
|
+
// schema. Unit tests construct the backend directly (bypassing this path)
|
|
33
|
+
// and stay on base queues.
|
|
34
|
+
const resolveQueueSuffixForSchema =
|
|
35
|
+
options.resolveQueueSuffixForSchema ?? resolveAbsurdQueueSuffixForSchema;
|
|
36
|
+
return createAbsurdSchedulerBackend({
|
|
37
|
+
url: options.postgresUrl?.trim() || null,
|
|
38
|
+
schema,
|
|
39
|
+
queueSuffix: resolveQueueSuffixForSchema(schema),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (options.postgresUrl?.trim()) {
|
|
43
|
+
return createAbsurdSchedulerBackend({
|
|
44
|
+
url: options.postgresUrl.trim(),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return absurdSchedulerBackend;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Unsupported scheduler backend: ${backend}`);
|
|
50
|
+
}
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/play-run-recovery.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isPlayRunRecoveryError,
|
|
3
|
+
PLAY_RUN_RECOVERY_DEFER_POLICY,
|
|
4
|
+
recoveryDeferDelaySeconds,
|
|
5
|
+
} from '../play-run-recovery-policy';
|
|
6
|
+
import type { PlayCheckpoint } from '../ctx-types';
|
|
7
|
+
import type { PlayRunnerResult } from '../protocol';
|
|
8
|
+
import {
|
|
9
|
+
deferPostgresSchedulerAttemptForRecovery,
|
|
10
|
+
type PostgresSchedulerOptions,
|
|
11
|
+
type PostgresSchedulerQueryClient,
|
|
12
|
+
} from './postgres';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Scheduler-owned transition for failures proved to have happened before
|
|
16
|
+
* customer code. Provider Adapters supply typed evidence; this Module owns the
|
|
17
|
+
* durable defer budget, dependency circuit decision, and exhausted outcome.
|
|
18
|
+
*/
|
|
19
|
+
export type PlayRunRecoveryOutcome =
|
|
20
|
+
| { kind: 'not_recoverable' }
|
|
21
|
+
| {
|
|
22
|
+
kind: 'deferred';
|
|
23
|
+
dependency: 'modal' | 'runtime';
|
|
24
|
+
reason: string;
|
|
25
|
+
deferCount: number;
|
|
26
|
+
deadlineAtMs: number | null;
|
|
27
|
+
delaySeconds: number;
|
|
28
|
+
}
|
|
29
|
+
| { kind: 'exhausted'; terminalResult: PlayRunnerResult };
|
|
30
|
+
|
|
31
|
+
export async function transitionPreCodePlayRunRecovery(input: {
|
|
32
|
+
error: unknown;
|
|
33
|
+
runId: string;
|
|
34
|
+
attempt: number;
|
|
35
|
+
leaseToken: string;
|
|
36
|
+
checkpoint: PlayCheckpoint | null;
|
|
37
|
+
client: PostgresSchedulerQueryClient;
|
|
38
|
+
schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
|
|
39
|
+
nowMs?: number;
|
|
40
|
+
}): Promise<PlayRunRecoveryOutcome> {
|
|
41
|
+
if (
|
|
42
|
+
!isPlayRunRecoveryError(input.error) ||
|
|
43
|
+
input.error.decision.stage !== 'pre_code' ||
|
|
44
|
+
input.error.decision.action !== 'defer'
|
|
45
|
+
) {
|
|
46
|
+
return { kind: 'not_recoverable' };
|
|
47
|
+
}
|
|
48
|
+
const decision = input.error.decision;
|
|
49
|
+
const recovery = await deferPostgresSchedulerAttemptForRecovery(
|
|
50
|
+
input.client,
|
|
51
|
+
{
|
|
52
|
+
runId: input.runId,
|
|
53
|
+
attempt: input.attempt,
|
|
54
|
+
leaseToken: input.leaseToken,
|
|
55
|
+
reason: decision.reason,
|
|
56
|
+
dependency: decision.dependency,
|
|
57
|
+
// Runtime-wide operator holds/throttles are already durable policy.
|
|
58
|
+
// Only Modal-observed outages own the provider circuit.
|
|
59
|
+
openCircuit:
|
|
60
|
+
decision.dependency === 'modal' &&
|
|
61
|
+
decision.reason !== 'modal_circuit_open_before_execution',
|
|
62
|
+
nowMs: input.nowMs ?? Date.now(),
|
|
63
|
+
maxDefers: PLAY_RUN_RECOVERY_DEFER_POLICY.maxDefers,
|
|
64
|
+
horizonMs: PLAY_RUN_RECOVERY_DEFER_POLICY.horizonMs,
|
|
65
|
+
deferBaseMs: PLAY_RUN_RECOVERY_DEFER_POLICY.deferBaseSeconds * 1_000,
|
|
66
|
+
deferMaxMs: PLAY_RUN_RECOVERY_DEFER_POLICY.deferMaxSeconds * 1_000,
|
|
67
|
+
},
|
|
68
|
+
input.schedulerOptions,
|
|
69
|
+
);
|
|
70
|
+
if (recovery.deferred && recovery.deferCount !== null) {
|
|
71
|
+
return {
|
|
72
|
+
kind: 'deferred',
|
|
73
|
+
dependency: decision.dependency,
|
|
74
|
+
reason: decision.reason,
|
|
75
|
+
deferCount: recovery.deferCount,
|
|
76
|
+
deadlineAtMs: recovery.deadlineAtMs,
|
|
77
|
+
delaySeconds: recoveryDeferDelaySeconds(recovery.deferCount),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (recovery.exhausted) {
|
|
81
|
+
return {
|
|
82
|
+
kind: 'exhausted',
|
|
83
|
+
terminalResult: {
|
|
84
|
+
status: 'failed',
|
|
85
|
+
error:
|
|
86
|
+
`PLATFORM_RECOVERY_EXHAUSTED: ${decision.dependency} ` +
|
|
87
|
+
'remained unavailable before customer execution began through the bounded recovery horizon. ' +
|
|
88
|
+
'No customer code was started; inspect and redrive this run after the dependency is healthy.',
|
|
89
|
+
logs: [],
|
|
90
|
+
stats: {},
|
|
91
|
+
steps: [],
|
|
92
|
+
checkpoint: input.checkpoint,
|
|
93
|
+
tableNamespace: null,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { kind: 'not_recoverable' };
|
|
98
|
+
}
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-budget-state.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { BudgetStateLimitError } from '../governor/budget-state-backend';
|
|
2
|
+
import type {
|
|
3
|
+
PlayRunnerBudgetChargeInput,
|
|
4
|
+
PlayRunnerBudgetChargeResult,
|
|
5
|
+
} from '../protocol';
|
|
6
|
+
import type { PostgresSchedulerQueryClient } from './postgres';
|
|
7
|
+
import { stringifyPostgresJson } from '../postgres-json';
|
|
8
|
+
|
|
9
|
+
const MAX_BUDGET_CHARGES_PER_REQUEST = 16;
|
|
10
|
+
|
|
11
|
+
function quoteIdent(value: string): string {
|
|
12
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function tableName(input: { schedulerSchema?: string | null }): string {
|
|
16
|
+
const schema = input.schedulerSchema?.trim() || 'runtime_scheduler';
|
|
17
|
+
return `${quoteIdent(schema)}.${quoteIdent('governor_budget_state')}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function reservationTableName(input: {
|
|
21
|
+
schedulerSchema?: string | null;
|
|
22
|
+
}): string {
|
|
23
|
+
const schema = input.schedulerSchema?.trim() || 'runtime_scheduler';
|
|
24
|
+
return `${quoteIdent(schema)}.${quoteIdent('governor_budget_reservations')}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function ensurePostgresBudgetStateSchema(
|
|
28
|
+
client: PostgresSchedulerQueryClient,
|
|
29
|
+
input: { schedulerSchema?: string | null } = {},
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
await client.query(`
|
|
32
|
+
CREATE TABLE IF NOT EXISTS ${tableName(input)} (
|
|
33
|
+
org_id text NOT NULL,
|
|
34
|
+
root_run_id text NOT NULL,
|
|
35
|
+
counters_json jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
36
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
37
|
+
PRIMARY KEY (org_id, root_run_id)
|
|
38
|
+
)
|
|
39
|
+
`);
|
|
40
|
+
await client.query(`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS ${reservationTableName(input)} (
|
|
42
|
+
org_id text NOT NULL,
|
|
43
|
+
root_run_id text NOT NULL,
|
|
44
|
+
reservation_id text NOT NULL,
|
|
45
|
+
charges_json jsonb NOT NULL,
|
|
46
|
+
counters_json jsonb,
|
|
47
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
48
|
+
PRIMARY KEY (org_id, root_run_id, reservation_id)
|
|
49
|
+
)
|
|
50
|
+
`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizedCharges(input: PlayRunnerBudgetChargeInput) {
|
|
54
|
+
if (input.charges.length > MAX_BUDGET_CHARGES_PER_REQUEST) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Governor budget accepts at most ${MAX_BUDGET_CHARGES_PER_REQUEST} charges per request.`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const merged = new Map<string, { amount: number; limit: number }>();
|
|
60
|
+
for (const charge of input.charges) {
|
|
61
|
+
const key = charge.key.trim();
|
|
62
|
+
if (!key || key.length > 256) {
|
|
63
|
+
throw new Error('Governor budget key must be 1-256 characters.');
|
|
64
|
+
}
|
|
65
|
+
if (!Number.isSafeInteger(charge.amount) || charge.amount <= 0) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Governor budget ${key} amount must be a positive integer.`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (!Number.isSafeInteger(charge.limit) || charge.limit <= 0) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Governor budget ${key} limit must be a positive integer.`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const current = merged.get(key);
|
|
76
|
+
merged.set(key, {
|
|
77
|
+
amount: (current?.amount ?? 0) + charge.amount,
|
|
78
|
+
limit: Math.min(current?.limit ?? charge.limit, charge.limit),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return [...merged.entries()].map(([key, value]) => ({ key, ...value }));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sameCharges(
|
|
85
|
+
left: Array<{ key: string; amount: number; limit: number }>,
|
|
86
|
+
right: Array<{ key: string; amount: number; limit: number }>,
|
|
87
|
+
): boolean {
|
|
88
|
+
return (
|
|
89
|
+
left.length === right.length &&
|
|
90
|
+
left.every((charge, index) => {
|
|
91
|
+
const other = right[index];
|
|
92
|
+
return (
|
|
93
|
+
other?.key === charge.key &&
|
|
94
|
+
Number(other.amount) === charge.amount &&
|
|
95
|
+
Number(other.limit) === charge.limit
|
|
96
|
+
);
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Atomically reserve one or more root-run counters. One row serializes sibling
|
|
102
|
+
* sandboxes, and a multi-counter child reservation either commits all or none. */
|
|
103
|
+
export async function chargePostgresBudgetState(
|
|
104
|
+
client: PostgresSchedulerQueryClient,
|
|
105
|
+
input: PlayRunnerBudgetChargeInput & { orgId: string },
|
|
106
|
+
): Promise<PlayRunnerBudgetChargeResult> {
|
|
107
|
+
const charges = normalizedCharges(input);
|
|
108
|
+
if (charges.length === 0) return { counters: {} };
|
|
109
|
+
const reservationId = input.reservationId.trim();
|
|
110
|
+
if (!reservationId || reservationId.length > 256) {
|
|
111
|
+
throw new Error('Governor budget reservationId must be 1-256 characters.');
|
|
112
|
+
}
|
|
113
|
+
for (const charge of charges) {
|
|
114
|
+
if (charge.amount > charge.limit) {
|
|
115
|
+
throw new BudgetStateLimitError(charge.key, charge.amount, charge.limit);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
await client.query('BEGIN');
|
|
119
|
+
try {
|
|
120
|
+
const insertedReservation = await client.query<{
|
|
121
|
+
reservation_id: string;
|
|
122
|
+
}>(
|
|
123
|
+
`
|
|
124
|
+
INSERT INTO ${reservationTableName(input)} (
|
|
125
|
+
org_id,
|
|
126
|
+
root_run_id,
|
|
127
|
+
reservation_id,
|
|
128
|
+
charges_json
|
|
129
|
+
) VALUES ($1, $2, $3, $4::jsonb)
|
|
130
|
+
ON CONFLICT (org_id, root_run_id, reservation_id) DO NOTHING
|
|
131
|
+
RETURNING reservation_id
|
|
132
|
+
`,
|
|
133
|
+
[
|
|
134
|
+
input.orgId,
|
|
135
|
+
input.rootRunId,
|
|
136
|
+
reservationId,
|
|
137
|
+
stringifyPostgresJson(charges),
|
|
138
|
+
],
|
|
139
|
+
);
|
|
140
|
+
if (!insertedReservation.rows[0]) {
|
|
141
|
+
const existing = await client.query<{
|
|
142
|
+
charges_json: typeof charges;
|
|
143
|
+
counters_json: Record<string, number> | null;
|
|
144
|
+
}>(
|
|
145
|
+
`
|
|
146
|
+
SELECT charges_json, counters_json
|
|
147
|
+
FROM ${reservationTableName(input)}
|
|
148
|
+
WHERE org_id = $1 AND root_run_id = $2 AND reservation_id = $3
|
|
149
|
+
FOR UPDATE
|
|
150
|
+
`,
|
|
151
|
+
[input.orgId, input.rootRunId, reservationId],
|
|
152
|
+
);
|
|
153
|
+
const reservation = existing.rows[0];
|
|
154
|
+
if (!reservation) {
|
|
155
|
+
throw new Error('Governor budget reservation disappeared.');
|
|
156
|
+
}
|
|
157
|
+
if (!sameCharges(reservation.charges_json, charges)) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
'Governor budget reservationId was reused with different charges.',
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (!reservation.counters_json) {
|
|
163
|
+
throw new Error('Governor budget reservation is incomplete.');
|
|
164
|
+
}
|
|
165
|
+
await client.query('COMMIT');
|
|
166
|
+
return { counters: reservation.counters_json };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const charged = await client.query<{
|
|
170
|
+
counters_json: Record<string, number>;
|
|
171
|
+
}>(
|
|
172
|
+
`
|
|
173
|
+
WITH charges AS (
|
|
174
|
+
SELECT *
|
|
175
|
+
FROM jsonb_to_recordset($3::jsonb)
|
|
176
|
+
AS charge(key text, amount bigint, "limit" bigint)
|
|
177
|
+
)
|
|
178
|
+
INSERT INTO ${tableName(input)} AS budget (
|
|
179
|
+
org_id,
|
|
180
|
+
root_run_id,
|
|
181
|
+
counters_json,
|
|
182
|
+
updated_at
|
|
183
|
+
)
|
|
184
|
+
SELECT $1,
|
|
185
|
+
$2,
|
|
186
|
+
jsonb_object_agg(charges.key, to_jsonb(charges.amount)),
|
|
187
|
+
now()
|
|
188
|
+
FROM charges
|
|
189
|
+
ON CONFLICT (org_id, root_run_id) DO UPDATE
|
|
190
|
+
SET counters_json = budget.counters_json || (
|
|
191
|
+
SELECT jsonb_object_agg(
|
|
192
|
+
charges.key,
|
|
193
|
+
to_jsonb(
|
|
194
|
+
COALESCE((budget.counters_json ->> charges.key)::bigint, 0) +
|
|
195
|
+
charges.amount
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
FROM charges
|
|
199
|
+
),
|
|
200
|
+
updated_at = now()
|
|
201
|
+
WHERE NOT EXISTS (
|
|
202
|
+
SELECT 1
|
|
203
|
+
FROM charges
|
|
204
|
+
WHERE COALESCE((budget.counters_json ->> charges.key)::bigint, 0) +
|
|
205
|
+
charges.amount > charges."limit"
|
|
206
|
+
)
|
|
207
|
+
RETURNING counters_json
|
|
208
|
+
`,
|
|
209
|
+
[input.orgId, input.rootRunId, stringifyPostgresJson(charges)],
|
|
210
|
+
);
|
|
211
|
+
const counters = charged.rows[0]?.counters_json;
|
|
212
|
+
if (counters) {
|
|
213
|
+
await client.query(
|
|
214
|
+
`
|
|
215
|
+
UPDATE ${reservationTableName(input)}
|
|
216
|
+
SET counters_json = $4::jsonb
|
|
217
|
+
WHERE org_id = $1 AND root_run_id = $2 AND reservation_id = $3
|
|
218
|
+
`,
|
|
219
|
+
[
|
|
220
|
+
input.orgId,
|
|
221
|
+
input.rootRunId,
|
|
222
|
+
reservationId,
|
|
223
|
+
stringifyPostgresJson(counters),
|
|
224
|
+
],
|
|
225
|
+
);
|
|
226
|
+
await client.query('COMMIT');
|
|
227
|
+
return { counters };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const current = await client.query<{
|
|
231
|
+
counters_json: Record<string, number>;
|
|
232
|
+
}>(
|
|
233
|
+
`
|
|
234
|
+
SELECT counters_json
|
|
235
|
+
FROM ${tableName(input)}
|
|
236
|
+
WHERE org_id = $1 AND root_run_id = $2
|
|
237
|
+
`,
|
|
238
|
+
[input.orgId, input.rootRunId],
|
|
239
|
+
);
|
|
240
|
+
const observedCounters = current.rows[0]?.counters_json ?? {};
|
|
241
|
+
const violation = charges.find(
|
|
242
|
+
(charge) =>
|
|
243
|
+
Number(observedCounters[charge.key] ?? 0) + charge.amount >
|
|
244
|
+
charge.limit,
|
|
245
|
+
);
|
|
246
|
+
if (!violation) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
'Governor budget reservation failed without a limit violation.',
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
throw new BudgetStateLimitError(
|
|
252
|
+
violation.key,
|
|
253
|
+
Number(observedCounters[violation.key] ?? 0) + violation.amount,
|
|
254
|
+
violation.limit,
|
|
255
|
+
);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|