deepline 0.1.225 → 0.1.227
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/apps/play-runner-workers/src/entry.ts +33 -17
- package/dist/bundling-sources/sdk/src/play.ts +5 -0
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/observability/tracing.ts +147 -22
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +80 -6
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +12 -1
- package/dist/bundling-sources/shared_libs/play-runtime/live-state-contract.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/postgres-json.ts +66 -4
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +103 -15
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +8 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +186 -45
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-transition.ts +126 -8
- package/dist/bundling-sources/shared_libs/play-runtime/step-lifecycle-tracker.ts +3 -1
- package/dist/cli/index.js +19 -2
- package/dist/cli/index.mjs +19 -2
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +7 -2
- package/dist/index.mjs +7 -2
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ const CLOUDFLARE_WORKFLOW_ENDPOINT_UNAVAILABLE_RE =
|
|
|
6
6
|
/The requested endpoint could not be found, or you don't have access to it\. Please check the provided ID and try again\./i;
|
|
7
7
|
const VERCEL_RUNTIME_API_DEPLOYMENT_MISSING_RE =
|
|
8
8
|
/runtime API 404:[\s\S]*(?:DeploymentNotFound|DEPLOYMENT_NOT_FOUND|requested deployment .*exist|deployment could not be found on Vercel)/i;
|
|
9
|
+
const RUNTIME_SANDBOX_LOST_RE = /\bRUNTIME_SANDBOX_LOST\b/i;
|
|
9
10
|
|
|
10
11
|
export const PLATFORM_DEPLOY_INTERRUPTED_MESSAGE =
|
|
11
12
|
'Run interrupted by a platform deploy. Deepline retries this automatically when possible; if this error is still visible, re-run the same command.';
|
|
@@ -16,6 +17,8 @@ export const PLATFORM_WORKFLOW_ENDPOINT_INTERRUPTED_MESSAGE =
|
|
|
16
17
|
|
|
17
18
|
export const INTERNAL_RUNTIME_STORAGE_ERROR_MESSAGE =
|
|
18
19
|
'Internal play runtime storage failed. Please retry the run; if this keeps happening, contact Deepline support with the run ID.';
|
|
20
|
+
export const RUNTIME_SANDBOX_LOST_MESSAGE =
|
|
21
|
+
'The execution sandbox became unreachable before returning a terminal result. Re-run the same command; if this keeps happening, contact Deepline support with the run ID.';
|
|
19
22
|
|
|
20
23
|
export const WORKSPACE_STORAGE_NOT_READY_CODE = 'WORKSPACE_STORAGE_NOT_READY';
|
|
21
24
|
|
|
@@ -90,11 +93,7 @@ export class ProviderExhaustedError extends Error {
|
|
|
90
93
|
readonly provider: string;
|
|
91
94
|
/** ISO 8601 retry deadline (from the server Retry-After / cooldown). */
|
|
92
95
|
readonly retryAt: string;
|
|
93
|
-
constructor(input: {
|
|
94
|
-
provider: string;
|
|
95
|
-
retryAtMs: number;
|
|
96
|
-
cause?: unknown;
|
|
97
|
-
}) {
|
|
96
|
+
constructor(input: { provider: string; retryAtMs: number; cause?: unknown }) {
|
|
98
97
|
// Prefix the code token so `String(error)` and any persisted error string
|
|
99
98
|
// carry it across the serialization boundary back to the CLI, exactly like
|
|
100
99
|
// WorkspaceStorageNotReadyError.
|
|
@@ -124,8 +123,7 @@ export class ProviderExhaustedError extends Error {
|
|
|
124
123
|
*/
|
|
125
124
|
export function providerNameFromBucketId(bucketId: string): string {
|
|
126
125
|
const separator = bucketId.indexOf(':');
|
|
127
|
-
const provider =
|
|
128
|
-
separator >= 0 ? bucketId.slice(separator + 1) : bucketId;
|
|
126
|
+
const provider = separator >= 0 ? bucketId.slice(separator + 1) : bucketId;
|
|
129
127
|
return provider.startsWith('tool:')
|
|
130
128
|
? provider.slice('tool:'.length)
|
|
131
129
|
: provider;
|
|
@@ -137,8 +135,66 @@ export type PlayRunFailureDetails = {
|
|
|
137
135
|
message: string;
|
|
138
136
|
retryable: boolean | null;
|
|
139
137
|
cause?: string;
|
|
138
|
+
name?: string;
|
|
139
|
+
stack?: string;
|
|
140
|
+
causes?: string[];
|
|
140
141
|
};
|
|
141
142
|
|
|
143
|
+
const PUBLIC_FAILURE_STACK_LINE_LIMIT = 12;
|
|
144
|
+
const PUBLIC_FAILURE_TEXT_BYTE_LIMIT = 64 * 1024;
|
|
145
|
+
const PUBLIC_FAILURE_CAUSE_LIMIT = 8;
|
|
146
|
+
const PUBLIC_FAILURE_CAUSE_LENGTH_LIMIT = 1_000;
|
|
147
|
+
|
|
148
|
+
function boundedFailureText(value: string): string {
|
|
149
|
+
const encoder = new TextEncoder();
|
|
150
|
+
if (encoder.encode(value).byteLength <= PUBLIC_FAILURE_TEXT_BYTE_LIMIT) {
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
let bytes = 0;
|
|
154
|
+
let output = '';
|
|
155
|
+
for (const character of value) {
|
|
156
|
+
const characterBytes = encoder.encode(character).byteLength;
|
|
157
|
+
if (bytes + characterBytes > PUBLIC_FAILURE_TEXT_BYTE_LIMIT) break;
|
|
158
|
+
output += character;
|
|
159
|
+
bytes += characterBytes;
|
|
160
|
+
}
|
|
161
|
+
return `${output}\n[truncated]`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function boundedFailureStack(error: Error): string | undefined {
|
|
165
|
+
if (typeof error.stack !== 'string' || !error.stack.trim()) return undefined;
|
|
166
|
+
return boundedFailureText(
|
|
167
|
+
error.stack.split('\n').slice(0, PUBLIC_FAILURE_STACK_LINE_LIMIT).join('\n'),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function failureCauseTexts(error: Error): string[] {
|
|
172
|
+
const queue: unknown[] = [];
|
|
173
|
+
const seen = new Set<unknown>([error]);
|
|
174
|
+
const causes: string[] = [];
|
|
175
|
+
const withCause = error as Error & { cause?: unknown };
|
|
176
|
+
if (withCause.cause !== undefined) queue.push(withCause.cause);
|
|
177
|
+
if (error instanceof AggregateError) queue.push(...error.errors);
|
|
178
|
+
|
|
179
|
+
while (queue.length > 0 && causes.length < PUBLIC_FAILURE_CAUSE_LIMIT) {
|
|
180
|
+
const current = queue.shift();
|
|
181
|
+
if (current === undefined || current === null || seen.has(current)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
seen.add(current);
|
|
185
|
+
const text = toErrorText(current).trim();
|
|
186
|
+
if (text && text !== error.message && !causes.includes(text)) {
|
|
187
|
+
causes.push(text.slice(0, PUBLIC_FAILURE_CAUSE_LENGTH_LIMIT));
|
|
188
|
+
}
|
|
189
|
+
if (current instanceof Error) {
|
|
190
|
+
const nested = current as Error & { cause?: unknown };
|
|
191
|
+
if (nested.cause !== undefined) queue.push(nested.cause);
|
|
192
|
+
if (current instanceof AggregateError) queue.push(...current.errors);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return causes;
|
|
196
|
+
}
|
|
197
|
+
|
|
142
198
|
function toErrorText(error: unknown): string {
|
|
143
199
|
if (error instanceof Error) {
|
|
144
200
|
return error.message;
|
|
@@ -147,8 +203,23 @@ function toErrorText(error: unknown): string {
|
|
|
147
203
|
}
|
|
148
204
|
|
|
149
205
|
export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
|
|
150
|
-
const
|
|
151
|
-
|
|
206
|
+
const rawCause = toErrorText(error);
|
|
207
|
+
const cause = boundedFailureText(rawCause);
|
|
208
|
+
if (RUNTIME_SANDBOX_LOST_RE.test(rawCause)) {
|
|
209
|
+
const stack = error instanceof Error ? boundedFailureStack(error) : null;
|
|
210
|
+
const causes = error instanceof Error ? failureCauseTexts(error) : [];
|
|
211
|
+
return {
|
|
212
|
+
code: 'RUNTIME_SANDBOX_LOST',
|
|
213
|
+
phase: 'infrastructure',
|
|
214
|
+
message: RUNTIME_SANDBOX_LOST_MESSAGE,
|
|
215
|
+
retryable: true,
|
|
216
|
+
cause,
|
|
217
|
+
...(error instanceof Error ? { name: error.name || 'Error' } : {}),
|
|
218
|
+
...(stack ? { stack } : {}),
|
|
219
|
+
...(causes.length > 0 ? { causes } : {}),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (CLOUDFLARE_DURABLE_OBJECT_RESET_RE.test(rawCause)) {
|
|
152
223
|
return {
|
|
153
224
|
code: 'PLATFORM_DEPLOY_INTERRUPTED',
|
|
154
225
|
phase: 'runtime',
|
|
@@ -157,7 +228,7 @@ export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
|
|
|
157
228
|
cause,
|
|
158
229
|
};
|
|
159
230
|
}
|
|
160
|
-
if (VERCEL_RUNTIME_API_DEPLOYMENT_MISSING_RE.test(
|
|
231
|
+
if (VERCEL_RUNTIME_API_DEPLOYMENT_MISSING_RE.test(rawCause)) {
|
|
161
232
|
return {
|
|
162
233
|
code: 'PLATFORM_DEPLOY_INTERRUPTED',
|
|
163
234
|
phase: 'runtime',
|
|
@@ -166,7 +237,7 @@ export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
|
|
|
166
237
|
cause,
|
|
167
238
|
};
|
|
168
239
|
}
|
|
169
|
-
if (CLOUDFLARE_WORKER_SUBREQUEST_LIMIT_RE.test(
|
|
240
|
+
if (CLOUDFLARE_WORKER_SUBREQUEST_LIMIT_RE.test(rawCause)) {
|
|
170
241
|
return {
|
|
171
242
|
code: 'PLATFORM_WORKER_SUBREQUEST_INTERRUPTED',
|
|
172
243
|
phase: 'runtime',
|
|
@@ -175,7 +246,7 @@ export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
|
|
|
175
246
|
cause,
|
|
176
247
|
};
|
|
177
248
|
}
|
|
178
|
-
if (CLOUDFLARE_WORKFLOW_ENDPOINT_UNAVAILABLE_RE.test(
|
|
249
|
+
if (CLOUDFLARE_WORKFLOW_ENDPOINT_UNAVAILABLE_RE.test(rawCause)) {
|
|
179
250
|
return {
|
|
180
251
|
code: 'PLATFORM_WORKFLOW_ENDPOINT_INTERRUPTED',
|
|
181
252
|
phase: 'runtime',
|
|
@@ -184,7 +255,7 @@ export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
|
|
|
184
255
|
cause,
|
|
185
256
|
};
|
|
186
257
|
}
|
|
187
|
-
const playDepthBudgetMatch =
|
|
258
|
+
const playDepthBudgetMatch = rawCause.match(
|
|
188
259
|
/Play execution playDepth budget exceeded \((\d+)\/(\d+)\)\./,
|
|
189
260
|
);
|
|
190
261
|
if (playDepthBudgetMatch) {
|
|
@@ -198,7 +269,7 @@ export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
|
|
|
198
269
|
}
|
|
199
270
|
if (
|
|
200
271
|
error instanceof ProviderExhaustedError ||
|
|
201
|
-
/\bPROVIDER_EXHAUSTED\b/.test(
|
|
272
|
+
/\bPROVIDER_EXHAUSTED\b/.test(rawCause)
|
|
202
273
|
) {
|
|
203
274
|
// The human message (provider + retry time) already lives in `cause` after
|
|
204
275
|
// the code token prefix; surface it verbatim minus the token so the run
|
|
@@ -218,7 +289,7 @@ export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
|
|
|
218
289
|
}
|
|
219
290
|
if (
|
|
220
291
|
error instanceof WorkspaceStorageNotReadyError ||
|
|
221
|
-
/\bWORKSPACE_STORAGE_NOT_READY\b/.test(
|
|
292
|
+
/\bWORKSPACE_STORAGE_NOT_READY\b/.test(rawCause)
|
|
222
293
|
) {
|
|
223
294
|
return {
|
|
224
295
|
code: WORKSPACE_STORAGE_NOT_READY_CODE,
|
|
@@ -242,10 +313,27 @@ export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
|
|
|
242
313
|
retryable: true,
|
|
243
314
|
};
|
|
244
315
|
}
|
|
316
|
+
const diagnostics =
|
|
317
|
+
error instanceof Error
|
|
318
|
+
? {
|
|
319
|
+
name: error.name || 'Error',
|
|
320
|
+
stack: boundedFailureStack(error),
|
|
321
|
+
causes: failureCauseTexts(error),
|
|
322
|
+
}
|
|
323
|
+
: null;
|
|
245
324
|
return {
|
|
246
325
|
code: 'RUN_FAILED',
|
|
247
326
|
phase: 'runtime',
|
|
248
327
|
message: cause,
|
|
249
328
|
retryable: null,
|
|
329
|
+
...(diagnostics
|
|
330
|
+
? {
|
|
331
|
+
name: diagnostics.name,
|
|
332
|
+
...(diagnostics.stack ? { stack: diagnostics.stack } : {}),
|
|
333
|
+
...(diagnostics.causes.length > 0
|
|
334
|
+
? { causes: diagnostics.causes }
|
|
335
|
+
: {}),
|
|
336
|
+
}
|
|
337
|
+
: {}),
|
|
250
338
|
};
|
|
251
339
|
}
|
|
@@ -78,6 +78,7 @@ export type PlayRunLedgerStepSnapshot = {
|
|
|
78
78
|
label?: string;
|
|
79
79
|
kind?: string;
|
|
80
80
|
status: PlayRunLedgerStepStatus;
|
|
81
|
+
error?: string | null;
|
|
81
82
|
artifactTableNamespace?: string | null;
|
|
82
83
|
startedAt?: number | null;
|
|
83
84
|
completedAt?: number | null;
|
|
@@ -480,6 +481,7 @@ export function normalizePlayRunLedgerSnapshot(
|
|
|
480
481
|
label: optionalString(rawStep.label),
|
|
481
482
|
kind: optionalString(rawStep.kind),
|
|
482
483
|
status,
|
|
484
|
+
error: optionalNullableString(rawStep.error),
|
|
483
485
|
artifactTableNamespace: optionalNullableString(
|
|
484
486
|
rawStep.artifactTableNamespace,
|
|
485
487
|
),
|
|
@@ -1255,6 +1257,7 @@ export function reducePlayRunLedgerEvent(
|
|
|
1255
1257
|
label: event.label ?? current?.label,
|
|
1256
1258
|
kind: event.kind ?? current?.kind,
|
|
1257
1259
|
status,
|
|
1260
|
+
error: current?.error ?? null,
|
|
1258
1261
|
artifactTableNamespace:
|
|
1259
1262
|
progress?.artifactTableNamespace ??
|
|
1260
1263
|
current?.artifactTableNamespace ??
|
|
@@ -1308,6 +1311,12 @@ export function reducePlayRunLedgerEvent(
|
|
|
1308
1311
|
label: event.label ?? current?.label,
|
|
1309
1312
|
kind: event.kind ?? current?.kind,
|
|
1310
1313
|
status,
|
|
1314
|
+
error:
|
|
1315
|
+
event.type === 'step.failed'
|
|
1316
|
+
? (event.error ?? current?.error ?? null)
|
|
1317
|
+
: preserveFailedStatus
|
|
1318
|
+
? (current?.error ?? null)
|
|
1319
|
+
: null,
|
|
1311
1320
|
artifactTableNamespace:
|
|
1312
1321
|
event.artifactTableNamespace ??
|
|
1313
1322
|
current?.artifactTableNamespace ??
|
|
@@ -46,6 +46,7 @@ export type PlayRunStreamNodeProgress = {
|
|
|
46
46
|
export type PlayRunStreamNodeState = {
|
|
47
47
|
nodeId: string;
|
|
48
48
|
status: 'idle' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
49
|
+
error?: string | null;
|
|
49
50
|
artifactTableNamespace?: string | null;
|
|
50
51
|
progress?: PlayRunStreamNodeProgress | null;
|
|
51
52
|
startedAt?: number | null;
|
|
@@ -167,6 +168,7 @@ function buildSnapshotFromLedger(
|
|
|
167
168
|
.map((step) => ({
|
|
168
169
|
nodeId: step.stepId,
|
|
169
170
|
status: step.status,
|
|
171
|
+
error: step.error ?? null,
|
|
170
172
|
artifactTableNamespace: step.artifactTableNamespace ?? null,
|
|
171
173
|
progress: step.progress
|
|
172
174
|
? {
|
|
@@ -19,6 +19,7 @@ type DaytonaPayloadEnvelope = {
|
|
|
19
19
|
schemaVersion: 1;
|
|
20
20
|
runnerCode: string;
|
|
21
21
|
artifactBundledCode: string;
|
|
22
|
+
artifactSourceMap: string;
|
|
22
23
|
config: PlayRunnerExecutionConfig;
|
|
23
24
|
/** Crash-containment epilogue script (see buildDaytonaCrashTerminalPusherSource). */
|
|
24
25
|
crashPusherCode: string;
|
|
@@ -50,10 +51,11 @@ function nodeMaterializePayloadCommand(input: {
|
|
|
50
51
|
runnerPath: string;
|
|
51
52
|
configPath: string;
|
|
52
53
|
artifactCodePath: string;
|
|
54
|
+
artifactSourceMapPath: string;
|
|
53
55
|
crashPusherPath: string;
|
|
54
56
|
}): string {
|
|
55
57
|
const script =
|
|
56
|
-
"const fs=require('node:fs');const zlib=require('node:zlib');const p=JSON.parse(zlib.gunzipSync(fs.readFileSync(process.argv[1])));if(p.schemaVersion!==1)throw new Error('Unsupported Daytona payload envelope');fs.writeFileSync(process.argv[2],p.runnerCode);fs.writeFileSync(process.argv[3],JSON.stringify(p.config));fs.writeFileSync(process.argv[4],p.artifactBundledCode);fs.writeFileSync(process.argv[5],p.crashPusherCode);";
|
|
58
|
+
"const fs=require('node:fs');const zlib=require('node:zlib');const p=JSON.parse(zlib.gunzipSync(fs.readFileSync(process.argv[1])));if(p.schemaVersion!==1)throw new Error('Unsupported Daytona payload envelope');fs.writeFileSync(process.argv[2],p.runnerCode);fs.writeFileSync(process.argv[3],JSON.stringify(p.config));fs.writeFileSync(process.argv[4],p.artifactBundledCode);fs.writeFileSync(process.argv[5],p.artifactSourceMap);fs.writeFileSync(process.argv[6],p.crashPusherCode);";
|
|
57
59
|
return [
|
|
58
60
|
'node',
|
|
59
61
|
'-e',
|
|
@@ -62,6 +64,7 @@ function nodeMaterializePayloadCommand(input: {
|
|
|
62
64
|
shellQuote(input.runnerPath),
|
|
63
65
|
shellQuote(input.configPath),
|
|
64
66
|
shellQuote(input.artifactCodePath),
|
|
67
|
+
shellQuote(input.artifactSourceMapPath),
|
|
65
68
|
shellQuote(input.crashPusherPath),
|
|
66
69
|
].join(' ');
|
|
67
70
|
}
|
|
@@ -236,6 +239,7 @@ export async function stageDaytonaRunnerPayload(input: {
|
|
|
236
239
|
const envelopePath = `${workDir}/deepline-play-payload-${randomUUID()}.json.gz`;
|
|
237
240
|
const configPath = `${workDir}/deepline-play-config-${randomUUID()}.json`;
|
|
238
241
|
const artifactCodePath = `${workDir}/deepline-play-artifact-${randomUUID()}.cjs`;
|
|
242
|
+
const artifactSourceMapPath = `${artifactCodePath}.map`;
|
|
239
243
|
const crashPusherPath = `${workDir}/deepline-play-crash-terminal-${randomUUID()}.cjs`;
|
|
240
244
|
|
|
241
245
|
const remoteMaterializedFiles: Record<string, string> = {};
|
|
@@ -306,6 +310,7 @@ export async function stageDaytonaRunnerPayload(input: {
|
|
|
306
310
|
artifactTransport: {
|
|
307
311
|
bundledCodePath: artifactCodePath,
|
|
308
312
|
bundledCodeEncoding: 'utf8',
|
|
313
|
+
sourceMapPath: artifactSourceMapPath,
|
|
309
314
|
},
|
|
310
315
|
workspaceRoot: input.workDir,
|
|
311
316
|
csvSourcePath: hasInlineCsv
|
|
@@ -330,6 +335,7 @@ export async function stageDaytonaRunnerPayload(input: {
|
|
|
330
335
|
schemaVersion: 1,
|
|
331
336
|
runnerCode: bundle,
|
|
332
337
|
artifactBundledCode: compactedArtifact.bundledCode,
|
|
338
|
+
artifactSourceMap: input.config.artifact.sourceMap,
|
|
333
339
|
config: remoteConfig,
|
|
334
340
|
crashPusherCode: buildDaytonaCrashTerminalPusherSource(),
|
|
335
341
|
}),
|
|
@@ -355,6 +361,7 @@ export async function stageDaytonaRunnerPayload(input: {
|
|
|
355
361
|
runnerPath,
|
|
356
362
|
configPath,
|
|
357
363
|
artifactCodePath,
|
|
364
|
+
artifactSourceMapPath,
|
|
358
365
|
crashPusherPath,
|
|
359
366
|
})} && DEEPLINE_PLAY_RUNNER_READY_PATH=${shellQuote(readyPath)} DEEPLINE_PLAY_RUNNER_PROGRESS_EVENT_PATH=${shellQuote(progressEventPath)} DEEPLINE_PLAY_RUNNER_STDOUT_EVENT_MODE=compact ${runnerTraceEnv}node ${shellQuote(runnerPath)} ${shellQuote(configPath)}`;
|
|
360
367
|
// Crash-containment epilogue: runs UNCONDITIONALLY after the runner exits and
|
|
@@ -80,6 +80,7 @@ import {
|
|
|
80
80
|
import type { MapRowOutcome } from './durability-store';
|
|
81
81
|
import {
|
|
82
82
|
normalizeRuntimeMapInputIndex,
|
|
83
|
+
prepareRuntimeSheetRowsForJsonTransport,
|
|
83
84
|
prepareRuntimeSheetRowTransitions,
|
|
84
85
|
type RuntimePreparedCompletedRow,
|
|
85
86
|
type RuntimePreparedFailedRow,
|
|
@@ -272,6 +273,8 @@ type RuntimeSheetPreparedRowDisposition = {
|
|
|
272
273
|
key: string;
|
|
273
274
|
disposition: RuntimeSheetPrepareDisposition;
|
|
274
275
|
};
|
|
276
|
+
|
|
277
|
+
type RuntimeSheetDatasetMode = 'upsert' | 'net_new';
|
|
275
278
|
const dbSessionCache = new Map<string, DbSessionCacheEntry>();
|
|
276
279
|
const dbSessionInFlight = new Map<string, Promise<CreateDbSessionResponse>>();
|
|
277
280
|
const postgresPools = new Map<string, RuntimePool>();
|
|
@@ -614,10 +617,22 @@ async function postRuntimeApi<TResponse>(
|
|
|
614
617
|
typeof parsed?.error === 'string'
|
|
615
618
|
? parsed.error
|
|
616
619
|
: `Runtime API request failed with status ${response.status}.`;
|
|
620
|
+
const errorCode = typeof parsed?.code === 'string' ? parsed.code : null;
|
|
621
|
+
const errorAction =
|
|
622
|
+
typeof parsed?.action === 'string' ? parsed.action : body.action;
|
|
623
|
+
const requestId = response.headers.get('x-deepline-request-id');
|
|
624
|
+
const diagnosticContext = [
|
|
625
|
+
`status ${response.status}`,
|
|
626
|
+
errorCode ? `code=${errorCode}` : null,
|
|
627
|
+
errorAction ? `action=${errorAction}` : null,
|
|
628
|
+
requestId ? `request_id=${requestId}` : null,
|
|
629
|
+
]
|
|
630
|
+
.filter((value): value is string => Boolean(value))
|
|
631
|
+
.join(', ');
|
|
617
632
|
throw new Error(
|
|
618
633
|
details
|
|
619
|
-
? `${errorMessage} (
|
|
620
|
-
: `${errorMessage} (
|
|
634
|
+
? `${errorMessage} (${diagnosticContext}): ${details}`
|
|
635
|
+
: `${errorMessage} (${diagnosticContext}).`,
|
|
621
636
|
);
|
|
622
637
|
}
|
|
623
638
|
|
|
@@ -2963,6 +2978,7 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
2963
2978
|
physicalUpsertSetSql: string;
|
|
2964
2979
|
outputPhysicalColumns: PhysicalSheetColumnProjection[];
|
|
2965
2980
|
force?: boolean;
|
|
2981
|
+
mode?: RuntimeSheetDatasetMode;
|
|
2966
2982
|
},
|
|
2967
2983
|
): Promise<{
|
|
2968
2984
|
inserted: number;
|
|
@@ -3365,6 +3381,121 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
3365
3381
|
return { inserted, rowDispositions };
|
|
3366
3382
|
}
|
|
3367
3383
|
|
|
3384
|
+
/**
|
|
3385
|
+
* Atomically admit source-table keys that have not completed. A completed row
|
|
3386
|
+
* is consumed forever; failed rows and rows owned by this logical run remain
|
|
3387
|
+
* recoverable. This is deliberately a separate insert-only path: deriving
|
|
3388
|
+
* "new" from a read before the normal insert would race a concurrent sourcing
|
|
3389
|
+
* run.
|
|
3390
|
+
*/
|
|
3391
|
+
async function prepareNetNewRuntimeSheetDatasetRows(
|
|
3392
|
+
client: RuntimeQueryClient,
|
|
3393
|
+
session: RuntimePostgresSession,
|
|
3394
|
+
input: {
|
|
3395
|
+
chunks: RuntimeDatasetRowEntry[][];
|
|
3396
|
+
runId: string;
|
|
3397
|
+
attemptId: string;
|
|
3398
|
+
attemptOwnerRunId: string;
|
|
3399
|
+
attemptExpiresAt: string;
|
|
3400
|
+
attemptSeq: number;
|
|
3401
|
+
writeVersion: number;
|
|
3402
|
+
physicalInsertColumnsSql: string;
|
|
3403
|
+
physicalInsertValuesSql: string;
|
|
3404
|
+
},
|
|
3405
|
+
): Promise<{
|
|
3406
|
+
inserted: number;
|
|
3407
|
+
rowDispositions: RuntimeSheetPreparedRowDisposition[];
|
|
3408
|
+
}> {
|
|
3409
|
+
let inserted = 0;
|
|
3410
|
+
const rowDispositions: RuntimeSheetPreparedRowDisposition[] = [];
|
|
3411
|
+
for (const chunk of input.chunks) {
|
|
3412
|
+
const { rows } = await client.query<{
|
|
3413
|
+
inserted_count: number;
|
|
3414
|
+
row_dispositions: RuntimeSheetPreparedRowDisposition[];
|
|
3415
|
+
}>(
|
|
3416
|
+
`WITH input_rows AS (
|
|
3417
|
+
SELECT DISTINCT ON (key_values._key)
|
|
3418
|
+
key_values._key, payload_values.payload, index_values._input_index
|
|
3419
|
+
FROM unnest($1::text[]) WITH ORDINALITY AS key_values(_key, ord)
|
|
3420
|
+
JOIN unnest($2::jsonb[]) WITH ORDINALITY AS payload_values(payload, ord)
|
|
3421
|
+
ON payload_values.ord = key_values.ord
|
|
3422
|
+
JOIN unnest($3::bigint[]) WITH ORDINALITY AS index_values(_input_index, ord)
|
|
3423
|
+
ON index_values.ord = key_values.ord
|
|
3424
|
+
ORDER BY key_values._key, key_values.ord
|
|
3425
|
+
),
|
|
3426
|
+
reclaimed_rows AS (
|
|
3427
|
+
UPDATE ${sheetTable(session)} AS target
|
|
3428
|
+
SET _status = 'pending',
|
|
3429
|
+
_run_id = $4::text,
|
|
3430
|
+
_input_index = input_rows._input_index,
|
|
3431
|
+
_attempt_id = $5::text,
|
|
3432
|
+
_attempt_owner_run_id = $6::text,
|
|
3433
|
+
_attempt_expires_at = $7::timestamptz,
|
|
3434
|
+
_attempt_seq = $8::integer,
|
|
3435
|
+
_write_version = $9::bigint,
|
|
3436
|
+
_writer_run_id = $4::text,
|
|
3437
|
+
_error = NULL,
|
|
3438
|
+
_updated_at = now(),
|
|
3439
|
+
_version = ${nextRuntimeSheetVersionExpression(session)}
|
|
3440
|
+
FROM input_rows
|
|
3441
|
+
WHERE target._key = input_rows._key
|
|
3442
|
+
AND (
|
|
3443
|
+
target._status = 'failed'
|
|
3444
|
+
OR (
|
|
3445
|
+
target._status IN ('pending', 'running')
|
|
3446
|
+
AND coalesce(target._attempt_owner_run_id, target._run_id) = $6::text
|
|
3447
|
+
)
|
|
3448
|
+
)
|
|
3449
|
+
RETURNING target._key
|
|
3450
|
+
),
|
|
3451
|
+
inserted_rows AS (
|
|
3452
|
+
INSERT INTO ${sheetTable(session)}
|
|
3453
|
+
(_key, _status, _run_id, _input_index, _attempt_id, _attempt_owner_run_id,
|
|
3454
|
+
_attempt_expires_at, _attempt_seq, _write_version, _writer_run_id${input.physicalInsertColumnsSql})
|
|
3455
|
+
SELECT _key, 'pending', $4::text, _input_index, $5::text, $6::text,
|
|
3456
|
+
$7::timestamptz, $8::integer, $9::bigint, $4::text${input.physicalInsertValuesSql}
|
|
3457
|
+
FROM input_rows
|
|
3458
|
+
ON CONFLICT (_key) DO NOTHING
|
|
3459
|
+
RETURNING _key
|
|
3460
|
+
),
|
|
3461
|
+
pending_rows AS (
|
|
3462
|
+
SELECT _key FROM inserted_rows
|
|
3463
|
+
UNION
|
|
3464
|
+
SELECT _key FROM reclaimed_rows
|
|
3465
|
+
)
|
|
3466
|
+
SELECT
|
|
3467
|
+
(SELECT count(*)::int FROM inserted_rows) AS inserted_count,
|
|
3468
|
+
coalesce(
|
|
3469
|
+
(SELECT jsonb_agg(jsonb_build_object('key', _key, 'disposition', 'pending')) FROM pending_rows),
|
|
3470
|
+
'[]'::jsonb
|
|
3471
|
+
) AS row_dispositions`,
|
|
3472
|
+
[
|
|
3473
|
+
chunk.map((entry) => entry.key),
|
|
3474
|
+
chunk.map((entry) => stringifyPostgresJson(entry.row)),
|
|
3475
|
+
chunk.map((entry) => entry.inputIndex),
|
|
3476
|
+
input.runId,
|
|
3477
|
+
input.attemptId,
|
|
3478
|
+
input.attemptOwnerRunId,
|
|
3479
|
+
input.attemptExpiresAt,
|
|
3480
|
+
input.attemptSeq,
|
|
3481
|
+
input.writeVersion,
|
|
3482
|
+
],
|
|
3483
|
+
);
|
|
3484
|
+
inserted += Number(rows[0]?.inserted_count ?? 0);
|
|
3485
|
+
if (Array.isArray(rows[0]?.row_dispositions)) {
|
|
3486
|
+
rowDispositions.push(
|
|
3487
|
+
...rows[0].row_dispositions.filter(
|
|
3488
|
+
(row): row is RuntimeSheetPreparedRowDisposition =>
|
|
3489
|
+
row != null &&
|
|
3490
|
+
typeof row.key === 'string' &&
|
|
3491
|
+
row.disposition === 'pending',
|
|
3492
|
+
),
|
|
3493
|
+
);
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
return { inserted, rowDispositions };
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3368
3499
|
async function buildRuntimeSheetDatasetStartResult(
|
|
3369
3500
|
client: RuntimeQueryClient,
|
|
3370
3501
|
session: RuntimePostgresSession,
|
|
@@ -5548,6 +5679,7 @@ export async function startRuntimeSheetDataset(
|
|
|
5548
5679
|
writeVersion?: number | null;
|
|
5549
5680
|
inputOffset?: number;
|
|
5550
5681
|
force?: boolean;
|
|
5682
|
+
mode?: RuntimeSheetDatasetMode;
|
|
5551
5683
|
},
|
|
5552
5684
|
): Promise<PrepareRuntimeSheetResult> {
|
|
5553
5685
|
if (context.dbSessionStrategy === 'gateway_only') {
|
|
@@ -5724,24 +5856,37 @@ export async function startRuntimeSheetDataset(
|
|
|
5724
5856
|
rows: rowEntries.length,
|
|
5725
5857
|
});
|
|
5726
5858
|
const prepareStartedAt = Date.now();
|
|
5727
|
-
const prepared =
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5859
|
+
const prepared =
|
|
5860
|
+
input.mode === 'net_new'
|
|
5861
|
+
? await prepareNetNewRuntimeSheetDatasetRows(client, session, {
|
|
5862
|
+
chunks,
|
|
5863
|
+
runId: input.runId,
|
|
5864
|
+
attemptId,
|
|
5865
|
+
attemptOwnerRunId,
|
|
5866
|
+
attemptExpiresAt,
|
|
5867
|
+
attemptSeq,
|
|
5868
|
+
writeVersion,
|
|
5869
|
+
physicalInsertColumnsSql,
|
|
5870
|
+
physicalInsertValuesSql,
|
|
5871
|
+
})
|
|
5872
|
+
: await prepareRuntimeSheetDatasetRows(client, session, {
|
|
5873
|
+
chunks,
|
|
5874
|
+
runId: input.runId,
|
|
5875
|
+
normalizedPlayName,
|
|
5876
|
+
normalizedTableNamespace,
|
|
5877
|
+
attemptId,
|
|
5878
|
+
attemptOwnerRunId,
|
|
5879
|
+
attemptExpiresAt,
|
|
5880
|
+
attemptSeq,
|
|
5881
|
+
writeVersion,
|
|
5882
|
+
physicalInsertColumnsSql,
|
|
5883
|
+
physicalInsertValuesSql,
|
|
5884
|
+
physicalRefreshSetSql:
|
|
5885
|
+
input.force === true ? physicalRefreshSetSql : '',
|
|
5886
|
+
physicalUpsertSetSql,
|
|
5887
|
+
outputPhysicalColumns,
|
|
5888
|
+
force: input.force === true,
|
|
5889
|
+
});
|
|
5745
5890
|
timings.push({
|
|
5746
5891
|
phase: 'prepare_rows_sql',
|
|
5747
5892
|
ms: Date.now() - prepareStartedAt,
|
|
@@ -6151,12 +6296,8 @@ async function completeRuntimeMapRowChunks(
|
|
|
6151
6296
|
for (const chunk of input.chunks) {
|
|
6152
6297
|
const chunkKeys = chunk.map((row) => row.key);
|
|
6153
6298
|
const chunkInputIndexes = chunk.map((row) => row.input_index);
|
|
6154
|
-
const chunkDataPatches = chunk.map((row) =>
|
|
6155
|
-
|
|
6156
|
-
);
|
|
6157
|
-
const chunkCellMetaPatches = chunk.map((row) =>
|
|
6158
|
-
stringifyPostgresJson(row.cell_meta_patch),
|
|
6159
|
-
);
|
|
6299
|
+
const chunkDataPatches = chunk.map((row) => row.data_patch_json);
|
|
6300
|
+
const chunkCellMetaPatches = chunk.map((row) => row.cell_meta_patch_json);
|
|
6160
6301
|
const targetChangedPatchedCellSql = changedPatchedCellSql(
|
|
6161
6302
|
'target',
|
|
6162
6303
|
'updates.data_patch',
|
|
@@ -6418,12 +6559,8 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
|
|
|
6418
6559
|
for (const chunk of input.chunks) {
|
|
6419
6560
|
const chunkKeys = chunk.map((row) => row.key);
|
|
6420
6561
|
const chunkInputIndexes = chunk.map((row) => row.input_index);
|
|
6421
|
-
const chunkDataPatches = chunk.map((row) =>
|
|
6422
|
-
|
|
6423
|
-
);
|
|
6424
|
-
const chunkCellMetaPatches = chunk.map((row) =>
|
|
6425
|
-
stringifyPostgresJson(row.cell_meta_patch),
|
|
6426
|
-
);
|
|
6562
|
+
const chunkDataPatches = chunk.map((row) => row.data_patch_json);
|
|
6563
|
+
const chunkCellMetaPatches = chunk.map((row) => row.cell_meta_patch_json);
|
|
6427
6564
|
const targetChangedPatchedCellSql = changedPatchedCellSql(
|
|
6428
6565
|
'target',
|
|
6429
6566
|
'updates.data_patch',
|
|
@@ -6661,12 +6798,8 @@ async function insertMissingCompletedMapRowChunks(
|
|
|
6661
6798
|
for (const chunk of input.chunks) {
|
|
6662
6799
|
const chunkKeys = chunk.map((row) => row.key);
|
|
6663
6800
|
const chunkInputIndexes = chunk.map((row) => row.input_index);
|
|
6664
|
-
const chunkDataPatches = chunk.map((row) =>
|
|
6665
|
-
|
|
6666
|
-
);
|
|
6667
|
-
const chunkCellMetaPatches = chunk.map((row) =>
|
|
6668
|
-
stringifyPostgresJson(row.cell_meta_patch),
|
|
6669
|
-
);
|
|
6801
|
+
const chunkDataPatches = chunk.map((row) => row.data_patch_json);
|
|
6802
|
+
const chunkCellMetaPatches = chunk.map((row) => row.cell_meta_patch_json);
|
|
6670
6803
|
const { rows } = await client.query<{ inserted: number }>(
|
|
6671
6804
|
`WITH input_rows AS (
|
|
6672
6805
|
SELECT key_values._key,
|
|
@@ -6833,12 +6966,8 @@ async function failRuntimeMapRowChunks(
|
|
|
6833
6966
|
for (const chunk of input.chunks) {
|
|
6834
6967
|
const chunkKeys = chunk.map((row) => row.key);
|
|
6835
6968
|
const chunkInputIndexes = chunk.map((row) => row.input_index);
|
|
6836
|
-
const chunkDataPatches = chunk.map((row) =>
|
|
6837
|
-
|
|
6838
|
-
);
|
|
6839
|
-
const chunkCellMetaPatches = chunk.map((row) =>
|
|
6840
|
-
stringifyPostgresJson(row.cell_meta_patch),
|
|
6841
|
-
);
|
|
6969
|
+
const chunkDataPatches = chunk.map((row) => row.data_patch_json);
|
|
6970
|
+
const chunkCellMetaPatches = chunk.map((row) => row.cell_meta_patch_json);
|
|
6842
6971
|
const chunkErrors = chunk.map((row) => row.error);
|
|
6843
6972
|
// Per-field failed-cell counts for the column summary, computed from the
|
|
6844
6973
|
// cell meta patches (a failed row records exactly which cell failed).
|
|
@@ -7064,9 +7193,21 @@ export async function completeRuntimeMapRows(
|
|
|
7064
7193
|
},
|
|
7065
7194
|
): Promise<RuntimeMapRowsWriteResult> {
|
|
7066
7195
|
if (context.dbSessionStrategy === 'gateway_only') {
|
|
7196
|
+
const keyedRows = input.rows.map((row) => {
|
|
7197
|
+
if (row.key) return row;
|
|
7198
|
+
const key = resolveMapRowOutcomeKey(
|
|
7199
|
+
row as unknown as Record<string, unknown>,
|
|
7200
|
+
);
|
|
7201
|
+
return key ? { ...row, key } : row;
|
|
7202
|
+
});
|
|
7203
|
+
const rows = prepareRuntimeSheetRowsForJsonTransport({
|
|
7204
|
+
rows: keyedRows,
|
|
7205
|
+
runId: input.runId,
|
|
7206
|
+
outputFields: input.outputFields ?? [],
|
|
7207
|
+
});
|
|
7067
7208
|
return await postRuntimeApi<RuntimeMapRowsWriteResult>(context, {
|
|
7068
7209
|
action: 'runtime_sheet_complete_map_rows',
|
|
7069
|
-
input,
|
|
7210
|
+
input: { ...input, rows },
|
|
7070
7211
|
});
|
|
7071
7212
|
}
|
|
7072
7213
|
if (input.rows.length === 0) {
|