deepline 0.3.128 → 0.3.129
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 +34 -9
- package/dist/bundling-sources/sdk/src/http.ts +22 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +22 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +265 -2
- package/dist/bundling-sources/shared_libs/play-runtime/bounded-batch-function.ts +682 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +21 -5
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5390 -1732
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +107 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
- package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
- package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
- package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
- package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
- package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
- package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
- package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +98 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +41 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +885 -73
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
- package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
- package/dist/cli/index.js +250 -38
- package/dist/cli/index.mjs +250 -38
- package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
- package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +231 -35
- package/dist/index.mjs +231 -35
- package/dist/install-integrity.json +31 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +65 -1
- 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
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox egress bandwidth probe.
|
|
3
|
+
*
|
|
4
|
+
* WHY: on the jai fixture the receipt writer's completion queue reached 23 s
|
|
5
|
+
* per receipt while the gateway's heavy lane sat at 11% of one slot: the
|
|
6
|
+
* flights themselves moved ~650 KB in ~5 s, about 1 Mbit/s per flow on a
|
|
7
|
+
* 95 ms path, and the whole run achieved 0.3 MB/s of wire. The RTT probe
|
|
8
|
+
* cannot tell a per-flow window limit from a per-sandbox egress cap, and the
|
|
9
|
+
* two have different fixes (more flights in parallel vs. fewer bytes). One
|
|
10
|
+
* upload of incompressible bytes as a single flow, then as several parallel
|
|
11
|
+
* flows, tells them apart at boot: parallel throughput scaling with flow
|
|
12
|
+
* count is a per-flow window; a flat sum is the link.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export const RUNTIME_GATEWAY_EGRESS_PROBE_PATH = '/egress-probe';
|
|
16
|
+
/** The sink discards bytes; a caller cannot make it hold more than this. */
|
|
17
|
+
export const RUNTIME_GATEWAY_EGRESS_PROBE_MAX_BYTES = 1024 * 1024;
|
|
18
|
+
export const EGRESS_BANDWIDTH_PROBE_SINGLE_BYTES = 256 * 1024;
|
|
19
|
+
export const EGRESS_BANDWIDTH_PROBE_PARALLEL_FLOWS = 4;
|
|
20
|
+
export const EGRESS_BANDWIDTH_PROBE_PARALLEL_BYTES_EACH = 128 * 1024;
|
|
21
|
+
/**
|
|
22
|
+
* At the 1 Mbit/s observed on jai the single flow alone takes ~2 s and the
|
|
23
|
+
* parallel set ~1-4 s; the budget bounds the boot cost when the link is worse.
|
|
24
|
+
*/
|
|
25
|
+
export const EGRESS_BANDWIDTH_PROBE_BUDGET_MS = 12_000;
|
|
26
|
+
|
|
27
|
+
export type EgressBandwidthObservation = {
|
|
28
|
+
target: string;
|
|
29
|
+
singleBytes: number;
|
|
30
|
+
singleMs: number | null;
|
|
31
|
+
parallelFlows: number;
|
|
32
|
+
parallelBytesEach: number;
|
|
33
|
+
parallelMs: number | null;
|
|
34
|
+
errorClass: string | null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function egressMbitPerSecond(
|
|
38
|
+
bytes: number,
|
|
39
|
+
ms: number | null,
|
|
40
|
+
): number | null {
|
|
41
|
+
if (ms === null || !(ms > 0) || !(bytes > 0)) return null;
|
|
42
|
+
return (bytes * 8) / (ms / 1000) / 1_000_000;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function formatEgressBandwidthLine(
|
|
46
|
+
observation: EgressBandwidthObservation,
|
|
47
|
+
): string {
|
|
48
|
+
const number = (value: number | null) =>
|
|
49
|
+
value === null ? 'none' : String(Math.round(value));
|
|
50
|
+
const rate = (value: number | null) =>
|
|
51
|
+
value === null ? 'none' : value.toFixed(2);
|
|
52
|
+
const parallelTotalBytes =
|
|
53
|
+
observation.parallelFlows * observation.parallelBytesEach;
|
|
54
|
+
return (
|
|
55
|
+
`[perf] egress_bandwidth target=${observation.target} ` +
|
|
56
|
+
`single_bytes=${observation.singleBytes} single_ms=${number(observation.singleMs)} ` +
|
|
57
|
+
`single_mbit=${rate(egressMbitPerSecond(observation.singleBytes, observation.singleMs))} ` +
|
|
58
|
+
`parallel_flows=${observation.parallelFlows} parallel_bytes_each=${observation.parallelBytesEach} ` +
|
|
59
|
+
`parallel_ms=${number(observation.parallelMs)} ` +
|
|
60
|
+
`parallel_mbit=${rate(egressMbitPerSecond(parallelTotalBytes, observation.parallelMs))}` +
|
|
61
|
+
(observation.errorClass ? ` error=${observation.errorClass}` : '')
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Count and discard a request body without buffering it. Stops reading past
|
|
67
|
+
* `maxBytes` so a client cannot make the sink do more than the cap of work.
|
|
68
|
+
*/
|
|
69
|
+
export async function readEgressProbeBody(
|
|
70
|
+
body: ReadableStream<Uint8Array> | null,
|
|
71
|
+
maxBytes: number,
|
|
72
|
+
): Promise<{ bytes: number; overLimit: boolean }> {
|
|
73
|
+
if (!body) return { bytes: 0, overLimit: false };
|
|
74
|
+
const reader = body.getReader();
|
|
75
|
+
let bytes = 0;
|
|
76
|
+
try {
|
|
77
|
+
while (true) {
|
|
78
|
+
const { done, value } = await reader.read();
|
|
79
|
+
if (done) break;
|
|
80
|
+
bytes += value?.byteLength ?? 0;
|
|
81
|
+
if (bytes > maxBytes) {
|
|
82
|
+
await reader.cancel();
|
|
83
|
+
return { bytes, overLimit: true };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} finally {
|
|
87
|
+
reader.releaseLock();
|
|
88
|
+
}
|
|
89
|
+
return { bytes, overLimit: false };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Incompressible payload so a compressing hop cannot flatter the number. */
|
|
93
|
+
export function egressProbePayload(bytes: number): Uint8Array {
|
|
94
|
+
const payload = new Uint8Array(bytes);
|
|
95
|
+
const chunk = 64 * 1024;
|
|
96
|
+
for (let offset = 0; offset < bytes; offset += chunk) {
|
|
97
|
+
globalThis.crypto.getRandomValues(
|
|
98
|
+
payload.subarray(offset, Math.min(bytes, offset + chunk)),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return payload;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function probeEgressBandwidth(input: {
|
|
105
|
+
target: string;
|
|
106
|
+
baseUrl: string;
|
|
107
|
+
fetchImpl: typeof fetch;
|
|
108
|
+
headers: Record<string, string>;
|
|
109
|
+
deadlineAtMs: number;
|
|
110
|
+
now?: () => number;
|
|
111
|
+
singleBytes?: number;
|
|
112
|
+
parallelFlows?: number;
|
|
113
|
+
parallelBytesEach?: number;
|
|
114
|
+
}): Promise<EgressBandwidthObservation> {
|
|
115
|
+
const now = input.now ?? (() => Date.now());
|
|
116
|
+
const url = `${input.baseUrl.replace(/\/$/, '')}${RUNTIME_GATEWAY_EGRESS_PROBE_PATH}`;
|
|
117
|
+
const singleBytes = input.singleBytes ?? EGRESS_BANDWIDTH_PROBE_SINGLE_BYTES;
|
|
118
|
+
const parallelFlows =
|
|
119
|
+
input.parallelFlows ?? EGRESS_BANDWIDTH_PROBE_PARALLEL_FLOWS;
|
|
120
|
+
const parallelBytesEach =
|
|
121
|
+
input.parallelBytesEach ?? EGRESS_BANDWIDTH_PROBE_PARALLEL_BYTES_EACH;
|
|
122
|
+
const observation: EgressBandwidthObservation = {
|
|
123
|
+
target: input.target,
|
|
124
|
+
singleBytes,
|
|
125
|
+
singleMs: null,
|
|
126
|
+
parallelFlows,
|
|
127
|
+
parallelBytesEach,
|
|
128
|
+
parallelMs: null,
|
|
129
|
+
errorClass: null,
|
|
130
|
+
};
|
|
131
|
+
const upload = async (bytes: number): Promise<void> => {
|
|
132
|
+
const remainingMs = input.deadlineAtMs - now();
|
|
133
|
+
if (remainingMs <= 0) throw new Error('egress probe budget exhausted');
|
|
134
|
+
const response = await input.fetchImpl(url, {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
headers: {
|
|
137
|
+
...input.headers,
|
|
138
|
+
'content-type': 'application/octet-stream',
|
|
139
|
+
},
|
|
140
|
+
// Use the exact ArrayBuffer accepted by DOM fetch. The fresh byte view
|
|
141
|
+
// prevents Node's generic ArrayBufferLike type from widening this into
|
|
142
|
+
// a SharedArrayBuffer-capable body, while preserving the probe payload.
|
|
143
|
+
body: Uint8Array.from(egressProbePayload(bytes)).buffer,
|
|
144
|
+
signal: AbortSignal.timeout(remainingMs),
|
|
145
|
+
});
|
|
146
|
+
await response.arrayBuffer();
|
|
147
|
+
if (!response.ok) throw new Error(`http_${response.status}`);
|
|
148
|
+
};
|
|
149
|
+
try {
|
|
150
|
+
const singleStartedAt = now();
|
|
151
|
+
await upload(singleBytes);
|
|
152
|
+
observation.singleMs = now() - singleStartedAt;
|
|
153
|
+
const parallelStartedAt = now();
|
|
154
|
+
await Promise.all(
|
|
155
|
+
Array.from({ length: parallelFlows }, () => upload(parallelBytesEach)),
|
|
156
|
+
);
|
|
157
|
+
observation.parallelMs = now() - parallelStartedAt;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
observation.errorClass =
|
|
160
|
+
error instanceof Error
|
|
161
|
+
? error.message.startsWith('http_')
|
|
162
|
+
? error.message
|
|
163
|
+
: error.constructor.name
|
|
164
|
+
: typeof error;
|
|
165
|
+
}
|
|
166
|
+
return observation;
|
|
167
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response-path stages emitted by the deployed integration execute endpoint.
|
|
3
|
+
*
|
|
4
|
+
* This is deliberately a small, additive observability contract: the runner
|
|
5
|
+
* treats a missing or malformed header as absent telemetry, never as a failed
|
|
6
|
+
* tool call. Keeping the names here lets the response writer and the runner
|
|
7
|
+
* agree without inventing a runtime protocol version.
|
|
8
|
+
*/
|
|
9
|
+
export const EXECUTE_SERVER_TIMING_STAGES = [
|
|
10
|
+
'auth',
|
|
11
|
+
'rate_limit',
|
|
12
|
+
'catalog',
|
|
13
|
+
'charge_open',
|
|
14
|
+
// Charge admission substages. `charge_open` keeps its original meaning — the
|
|
15
|
+
// TOTAL of all charge-admission work — so an existing reader is unchanged and
|
|
16
|
+
// these four only explain where that total went. They are disjoint slices of
|
|
17
|
+
// it, and their sum can be below it (the difference is app-side work between
|
|
18
|
+
// the round trips), never above.
|
|
19
|
+
// - `charge_eligibility` the fast-postpaid eligibility snapshot lookup
|
|
20
|
+
// (Redis GET, then a Convex query on a miss)
|
|
21
|
+
// - `charge_cap_preflight` the `preflightChargeCap` mutation, its retries
|
|
22
|
+
// included
|
|
23
|
+
// - `charge_open_mutation` the `openCharge` mutation, its retries included
|
|
24
|
+
// - `charge_retry_wait` wall slept in write-conflict/OCC/capacity-shed
|
|
25
|
+
// backoff across both mutations. A high value here
|
|
26
|
+
// is same-org admission contention, not Convex
|
|
27
|
+
// latency, and is the only way to tell them apart.
|
|
28
|
+
'charge_eligibility',
|
|
29
|
+
'charge_cap_preflight',
|
|
30
|
+
'charge_open_mutation',
|
|
31
|
+
'charge_retry_wait',
|
|
32
|
+
'fixture_load',
|
|
33
|
+
'execute',
|
|
34
|
+
'identity',
|
|
35
|
+
'usage_schedule',
|
|
36
|
+
'total',
|
|
37
|
+
// The execution gateway preserves these disjoint stages beside the app
|
|
38
|
+
// handler's stages. They are deliberately part of this small, additive
|
|
39
|
+
// response diagnostic contract: a long sandbox-to-headers wall is otherwise
|
|
40
|
+
// incorrectly reduced to "network" after the app handler reports quickly.
|
|
41
|
+
'auth_launch',
|
|
42
|
+
'auth_verify',
|
|
43
|
+
'body_parse',
|
|
44
|
+
'dispatch_pool',
|
|
45
|
+
'dispatch_callback',
|
|
46
|
+
// The execution relay measures its own upstream fetch. `upstream_headers`
|
|
47
|
+
// contains the app handler's `total`; subtracting those two clocks leaves
|
|
48
|
+
// only the Vercel/platform-or-transit boundary, never a made-up provider
|
|
49
|
+
// latency. `upstream_body` follows headers while the relay buffers the
|
|
50
|
+
// bounded response before it returns to the runner.
|
|
51
|
+
'upstream_headers',
|
|
52
|
+
'upstream_body',
|
|
53
|
+
] as const;
|
|
54
|
+
|
|
55
|
+
export type ExecuteServerTimingStage =
|
|
56
|
+
(typeof EXECUTE_SERVER_TIMING_STAGES)[number];
|
|
57
|
+
|
|
58
|
+
export type ExecuteServerTiming = Record<
|
|
59
|
+
ExecuteServerTimingStage,
|
|
60
|
+
number | null
|
|
61
|
+
>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Read the known `Server-Timing` durations from an integration execute
|
|
65
|
+
* response. Browser/proxy changes to this diagnostic header must never change
|
|
66
|
+
* execution behavior, so malformed, negative, and absent values remain null.
|
|
67
|
+
*/
|
|
68
|
+
export function parseExecuteServerTiming(
|
|
69
|
+
header: string | null | undefined,
|
|
70
|
+
): ExecuteServerTiming | null {
|
|
71
|
+
if (typeof header !== 'string' || !header.trim()) return null;
|
|
72
|
+
const durations = new Map<string, number>();
|
|
73
|
+
for (const rawEntry of header.split(',')) {
|
|
74
|
+
const parts = rawEntry.split(';');
|
|
75
|
+
const name = parts[0]?.trim().toLowerCase();
|
|
76
|
+
if (!name) continue;
|
|
77
|
+
for (const parameter of parts.slice(1)) {
|
|
78
|
+
const separator = parameter.indexOf('=');
|
|
79
|
+
if (separator < 0) continue;
|
|
80
|
+
const key = parameter.slice(0, separator).trim().toLowerCase();
|
|
81
|
+
if (key !== 'dur') continue;
|
|
82
|
+
const value = parameter
|
|
83
|
+
.slice(separator + 1)
|
|
84
|
+
.trim()
|
|
85
|
+
.replace(/^"|"$/g, '');
|
|
86
|
+
// `Number('')` is 0; do not turn a truncated header into a confident
|
|
87
|
+
// measurement. Server-Timing durations are non-negative by definition.
|
|
88
|
+
const parsed = value === '' ? Number.NaN : Number(value);
|
|
89
|
+
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
90
|
+
durations.set(name, parsed);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const timing = Object.fromEntries(
|
|
95
|
+
EXECUTE_SERVER_TIMING_STAGES.map((stage) => [
|
|
96
|
+
stage,
|
|
97
|
+
durations.get(stage) ?? null,
|
|
98
|
+
]),
|
|
99
|
+
) as ExecuteServerTiming;
|
|
100
|
+
return EXECUTE_SERVER_TIMING_STAGES.some((stage) => timing[stage] !== null)
|
|
101
|
+
? timing
|
|
102
|
+
: null;
|
|
103
|
+
}
|
package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isRuntimeReceiptValueReference,
|
|
3
|
+
type RuntimeReceiptValueReference,
|
|
4
|
+
} from './runtime-receipt-value';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A trusted execution relay sets this only after it has written the complete
|
|
8
|
+
* serialized ToolExecuteResult under the scoped result-receipt key. It is an
|
|
9
|
+
* optimization hint: callers retain the normal provider response and may
|
|
10
|
+
* always fall back to the legacy completion payload when it is absent.
|
|
11
|
+
*/
|
|
12
|
+
export const EXECUTION_GATEWAY_RECEIPT_OUTPUT_REFERENCE_HEADER =
|
|
13
|
+
'x-deepline-runtime-receipt-output-reference';
|
|
14
|
+
|
|
15
|
+
export function encodeExecutionGatewayReceiptOutputReference(
|
|
16
|
+
reference: RuntimeReceiptValueReference,
|
|
17
|
+
): string {
|
|
18
|
+
return Buffer.from(JSON.stringify(reference), 'utf8').toString('base64url');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function parseExecutionGatewayReceiptOutputReference(
|
|
22
|
+
value: string | null | undefined,
|
|
23
|
+
): RuntimeReceiptValueReference | null {
|
|
24
|
+
const encoded = value?.trim();
|
|
25
|
+
if (!encoded || encoded.length > 8 * 1024) return null;
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(
|
|
28
|
+
Buffer.from(encoded, 'base64url').toString('utf8'),
|
|
29
|
+
) as unknown;
|
|
30
|
+
return isRuntimeReceiptValueReference(parsed) ? parsed : null;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The execution gateway buffers an upstream response before it relays it to a
|
|
3
|
+
* sandbox. `content-length` is not safe to forward: it can describe the
|
|
4
|
+
* upstream compressed representation rather than the decoded bytes the
|
|
5
|
+
* sandbox will materialize. This additive header instead describes the exact
|
|
6
|
+
* decoded ArrayBuffer the gateway has already bounded and is about to send.
|
|
7
|
+
*
|
|
8
|
+
* An older gateway simply omits the header. Callers must retain their
|
|
9
|
+
* conservative sizing fallback in that case; this is an observation, never a
|
|
10
|
+
* required protocol version.
|
|
11
|
+
*/
|
|
12
|
+
export const EXECUTION_GATEWAY_DECODED_BODY_BYTES_HEADER =
|
|
13
|
+
'x-deepline-execution-gateway-decoded-body-bytes';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse the exact decoded size attached by a trusted execution gateway.
|
|
17
|
+
* Reject every lossy or unbounded representation before it can lower a memory
|
|
18
|
+
* reservation.
|
|
19
|
+
*/
|
|
20
|
+
export function executionGatewayDecodedBodyBytes(
|
|
21
|
+
headers: Headers,
|
|
22
|
+
): number | null {
|
|
23
|
+
const raw = headers.get(EXECUTION_GATEWAY_DECODED_BODY_BYTES_HEADER)?.trim();
|
|
24
|
+
if (!raw || !/^(?:0|[1-9][0-9]*)$/.test(raw)) return null;
|
|
25
|
+
const bytes = Number(raw);
|
|
26
|
+
return Number.isSafeInteger(bytes) ? bytes : null;
|
|
27
|
+
}
|
|
@@ -3,11 +3,21 @@ export type GatewayPostgresLane = 'ordinary' | 'commit';
|
|
|
3
3
|
export const GATEWAY_POSTGRES_MAX_ORDINARY_ACTIVE = 8;
|
|
4
4
|
export const GATEWAY_POSTGRES_MAX_TOTAL_ACTIVE = 10;
|
|
5
5
|
export const GATEWAY_POSTGRES_MAX_CONSECUTIVE_COMMIT_PRIORITY_GRANTS = 8;
|
|
6
|
+
/**
|
|
7
|
+
* A parked waiter costs a request slot, its request body, and its abort
|
|
8
|
+
* plumbing for as long as it waits. Under a large map every Machine can be
|
|
9
|
+
* offered far more concurrent work than its pooled clients can ever drain, so
|
|
10
|
+
* an unbounded queue converts back pressure into unbounded memory and a wait
|
|
11
|
+
* no caller is still around for. Past this depth the gateway sheds instead:
|
|
12
|
+
* the provider has not been called, so the caller may retry immediately.
|
|
13
|
+
*/
|
|
14
|
+
export const GATEWAY_POSTGRES_MAX_WAITING_PER_LANE = 256;
|
|
6
15
|
|
|
7
16
|
export type GatewayPostgresAdmissionSnapshot = {
|
|
8
17
|
active: Record<GatewayPostgresLane, number> & { total: number };
|
|
9
18
|
waiting: Record<GatewayPostgresLane, number>;
|
|
10
19
|
cancelled: number;
|
|
20
|
+
overloaded: number;
|
|
11
21
|
commitPriorityGrants: number;
|
|
12
22
|
};
|
|
13
23
|
|
|
@@ -18,6 +28,32 @@ export class GatewayPostgresAdmissionCancelledError extends Error {
|
|
|
18
28
|
}
|
|
19
29
|
}
|
|
20
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Load shed at the admission boundary. It is only ever thrown before a waiter
|
|
33
|
+
* is enqueued, so no pooled client, statement, or provider call exists for the
|
|
34
|
+
* rejected request. Callers must map it to a retryable back-pressure response.
|
|
35
|
+
*/
|
|
36
|
+
export class GatewayPostgresAdmissionOverloadedError extends Error {
|
|
37
|
+
readonly code = 'GATEWAY_POSTGRES_ADMISSION_OVERLOADED';
|
|
38
|
+
readonly lane: GatewayPostgresLane;
|
|
39
|
+
readonly waiting: number;
|
|
40
|
+
readonly limit: number;
|
|
41
|
+
|
|
42
|
+
constructor(input: {
|
|
43
|
+
lane: GatewayPostgresLane;
|
|
44
|
+
waiting: number;
|
|
45
|
+
limit: number;
|
|
46
|
+
}) {
|
|
47
|
+
super(
|
|
48
|
+
`Gateway Postgres ${input.lane} admission queue is full (${input.waiting} waiting, limit ${input.limit}).`,
|
|
49
|
+
);
|
|
50
|
+
this.name = 'GatewayPostgresAdmissionOverloadedError';
|
|
51
|
+
this.lane = input.lane;
|
|
52
|
+
this.waiting = input.waiting;
|
|
53
|
+
this.limit = input.limit;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
21
57
|
type Waiter = {
|
|
22
58
|
lane: GatewayPostgresLane;
|
|
23
59
|
signal: AbortSignal | null;
|
|
@@ -35,6 +71,7 @@ export class GatewayPostgresAdmission {
|
|
|
35
71
|
#ordinaryActive = 0;
|
|
36
72
|
#commitActive = 0;
|
|
37
73
|
#cancelled = 0;
|
|
74
|
+
#overloaded = 0;
|
|
38
75
|
#commitPriorityGrants = 0;
|
|
39
76
|
#consecutiveCommitPriorityGrants = 0;
|
|
40
77
|
|
|
@@ -42,8 +79,19 @@ export class GatewayPostgresAdmission {
|
|
|
42
79
|
private readonly limits: {
|
|
43
80
|
maxOrdinaryActive?: number;
|
|
44
81
|
maxTotalActive?: number;
|
|
82
|
+
maxWaitingPerLane?: number;
|
|
45
83
|
} = {},
|
|
46
|
-
) {
|
|
84
|
+
) {
|
|
85
|
+
if (
|
|
86
|
+
limits.maxWaitingPerLane !== undefined &&
|
|
87
|
+
(!Number.isSafeInteger(limits.maxWaitingPerLane) ||
|
|
88
|
+
limits.maxWaitingPerLane < 1)
|
|
89
|
+
) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
'Gateway Postgres admission maxWaitingPerLane must be a positive integer.',
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
47
95
|
|
|
48
96
|
async acquire(
|
|
49
97
|
lane: GatewayPostgresLane,
|
|
@@ -56,6 +104,16 @@ export class GatewayPostgresAdmission {
|
|
|
56
104
|
if (this.#canGrant(lane) && this.#waiting.commit.length === 0) {
|
|
57
105
|
return this.#grant(lane);
|
|
58
106
|
}
|
|
107
|
+
const waiting = this.#waiting[lane].length;
|
|
108
|
+
const limit = this.#maxWaitingPerLane();
|
|
109
|
+
if (waiting >= limit) {
|
|
110
|
+
this.#overloaded += 1;
|
|
111
|
+
throw new GatewayPostgresAdmissionOverloadedError({
|
|
112
|
+
lane,
|
|
113
|
+
waiting,
|
|
114
|
+
limit,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
59
117
|
|
|
60
118
|
return await new Promise<() => void>((resolve, reject) => {
|
|
61
119
|
const waiter: Waiter = {
|
|
@@ -93,6 +151,7 @@ export class GatewayPostgresAdmission {
|
|
|
93
151
|
commit: this.#waiting.commit.length,
|
|
94
152
|
},
|
|
95
153
|
cancelled: this.#cancelled,
|
|
154
|
+
overloaded: this.#overloaded,
|
|
96
155
|
commitPriorityGrants: this.#commitPriorityGrants,
|
|
97
156
|
};
|
|
98
157
|
}
|
|
@@ -112,6 +171,12 @@ export class GatewayPostgresAdmission {
|
|
|
112
171
|
return this.limits.maxTotalActive ?? GATEWAY_POSTGRES_MAX_TOTAL_ACTIVE;
|
|
113
172
|
}
|
|
114
173
|
|
|
174
|
+
#maxWaitingPerLane(): number {
|
|
175
|
+
return (
|
|
176
|
+
this.limits.maxWaitingPerLane ?? GATEWAY_POSTGRES_MAX_WAITING_PER_LANE
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
115
180
|
#maxOrdinaryActive(): number {
|
|
116
181
|
return (
|
|
117
182
|
this.limits.maxOrdinaryActive ?? GATEWAY_POSTGRES_MAX_ORDINARY_ACTIVE
|