deepline 0.3.90 → 0.3.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +101 -6
  2. package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +2 -556
  3. package/dist/bundling-sources/sdk/src/monitor-fleets.ts +2 -2
  4. package/dist/bundling-sources/sdk/src/release.ts +7 -12
  5. package/dist/bundling-sources/sdk/src/types.ts +6 -1
  6. package/dist/bundling-sources/shared_libs/integration-contracts/monitor-fleet-contract.ts +549 -0
  7. package/dist/bundling-sources/shared_libs/monitors/fleets/errors.ts +96 -0
  8. package/dist/bundling-sources/shared_libs/monitors/fleets/limits.ts +8 -0
  9. package/dist/bundling-sources/shared_libs/monitors/fleets/plan.ts +320 -0
  10. package/dist/bundling-sources/shared_libs/monitors/fleets/verdict.ts +106 -0
  11. package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +5 -1
  12. package/dist/bundling-sources/shared_libs/monitors/monitor-fleet-limits.ts +2 -12
  13. package/dist/bundling-sources/shared_libs/monitors/package.json +107 -5
  14. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +12 -7
  15. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +36 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +1 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/db-session-lease-store.ts +578 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +521 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-receipt-gateway.ts +1378 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/runtime-topology.ts +1024 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-gateway-route-probe.ts +193 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machine-lifecycle.ts +104 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +1844 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +21 -0
  25. package/dist/bundling-sources/shared_libs/play-runtime/deployment/process-tree.ts +40 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +262 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-surface.ts +98 -0
  28. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +303 -0
  29. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-tuple-publication-guardian.ts +70 -0
  30. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-tuple-recovery.ts +519 -0
  31. package/dist/bundling-sources/shared_libs/play-runtime/deployment/vercel-fly-rollback-alignment.ts +280 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/deployment/vercel-production-gate.ts +159 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/memory-store.ts +114 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +44 -5
  35. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/child-play-resolver.ts +26 -0
  36. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +3662 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/progress-sidecar.ts +47 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +84 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-gateway-transport.ts +91 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-heartbeat.ts +424 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +321 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-sheet-persistence-retry.ts +110 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +625 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +46 -3
  45. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +1 -1
  46. package/dist/bundling-sources/shared_libs/play-runtime/webhook-receipt-retry.ts +50 -0
  47. package/dist/bundling-sources/shared_libs/plays/bundling/repository-adapter.ts +24 -0
  48. package/dist/bundling-sources/shared_libs/plays/child-run-projection.ts +174 -0
  49. package/dist/bundling-sources/shared_libs/plays/deploy/api-client.ts +267 -0
  50. package/dist/bundling-sources/shared_libs/plays/deploy/control-plane-types.ts +73 -0
  51. package/dist/bundling-sources/shared_libs/plays/deploy/manifest.ts +162 -0
  52. package/dist/bundling-sources/shared_libs/plays/deploy/project-serializer.ts +87 -0
  53. package/dist/bundling-sources/shared_libs/plays/deploy/source-file-path.ts +26 -0
  54. package/dist/bundling-sources/shared_libs/plays/deploy/target-paths.ts +36 -0
  55. package/dist/bundling-sources/shared_libs/plays/launch/input-contract.ts +400 -0
  56. package/dist/bundling-sources/shared_libs/plays/launch/prepare.ts +96 -0
  57. package/dist/bundling-sources/shared_libs/plays/local-file-discovery.ts +385 -0
  58. package/dist/bundling-sources/shared_libs/plays/play-grep-search.ts +79 -6
  59. package/dist/bundling-sources/shared_libs/plays/play-reference.ts +117 -0
  60. package/dist/bundling-sources/shared_libs/plays/run-input-summary.ts +260 -0
  61. package/dist/bundling-sources/shared_libs/plays/run-summary-types.ts +49 -0
  62. package/dist/bundling-sources/shared_libs/plays/secrets/errors.ts +19 -0
  63. package/dist/bundling-sources/shared_libs/plays/sheet-filter.ts +347 -0
  64. package/dist/bundling-sources/shared_libs/plays/standard-webhooks.ts +117 -0
  65. package/dist/bundling-sources/shared_libs/plays/status-result.ts +388 -0
  66. package/dist/bundling-sources/shared_libs/plays/transform-factor-structure.ts +377 -0
  67. package/dist/bundling-sources/shared_libs/plays/webhook-payload.ts +113 -0
  68. package/dist/cli/index.js +369 -162
  69. package/dist/cli/index.mjs +369 -162
  70. package/dist/{compiler-manifest-BaJfE-5H.d.mts → compiler-manifest-x14KL83E.d.mts} +1 -1
  71. package/dist/{compiler-manifest-BaJfE-5H.d.ts → compiler-manifest-x14KL83E.d.ts} +1 -1
  72. package/dist/index.d.mts +45 -8
  73. package/dist/index.d.ts +45 -8
  74. package/dist/index.js +61 -14
  75. package/dist/index.mjs +61 -14
  76. package/dist/install-integrity.json +54 -2
  77. package/dist/monitor-fleet-contract.d.mts +3 -10
  78. package/dist/monitor-fleet-contract.d.ts +3 -10
  79. package/dist/monitor-fleet-contract.js +2 -0
  80. package/dist/monitor-fleet-contract.mjs +1 -1
  81. package/dist/plays/bundle-play-file.d.mts +2 -2
  82. package/dist/plays/bundle-play-file.d.ts +2 -2
  83. package/dist/release.d.mts +3 -3
  84. package/dist/release.d.ts +3 -3
  85. package/dist/release.js +7 -12
  86. package/dist/release.mjs +7 -12
  87. package/package.json +2 -2
@@ -0,0 +1,117 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ import type { PlayStandardWebhookHeaderFamily } from './/authoring-contract';
3
+
4
+ export const DEFAULT_STANDARD_WEBHOOK_TOLERANCE_SECONDS = 300;
5
+
6
+ export type StandardWebhookVerificationFailure =
7
+ | 'missing_headers'
8
+ | 'invalid_event_id'
9
+ | 'invalid_timestamp'
10
+ | 'timestamp_out_of_tolerance'
11
+ | 'invalid_signature'
12
+ | 'invalid_secret';
13
+
14
+ export type StandardWebhookVerification =
15
+ | { ok: true; eventId: string; timestamp: number }
16
+ | { ok: false; reason: StandardWebhookVerificationFailure };
17
+
18
+ function headerNames(headerFamily: PlayStandardWebhookHeaderFamily) {
19
+ return headerFamily === 'svix'
20
+ ? {
21
+ id: 'svix-id',
22
+ timestamp: 'svix-timestamp',
23
+ signature: 'svix-signature',
24
+ }
25
+ : {
26
+ id: 'webhook-id',
27
+ timestamp: 'webhook-timestamp',
28
+ signature: 'webhook-signature',
29
+ };
30
+ }
31
+
32
+ function normalizedHeaders(headers: Record<string, string>) {
33
+ return Object.fromEntries(
34
+ Object.entries(headers).map(([name, value]) => [name.toLowerCase(), value]),
35
+ );
36
+ }
37
+
38
+ /** Standard Webhooks symmetric secrets are base64; providers commonly prefix them with `whsec_`. */
39
+ function decodeSigningSecret(value: string): Buffer | null {
40
+ const serialized = value.trim();
41
+ const encoded = serialized.startsWith('whsec_')
42
+ ? serialized.slice('whsec_'.length)
43
+ : serialized;
44
+ if (!encoded || !/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) return null;
45
+ const unpadded = encoded.replace(/=+$/, '');
46
+ if (unpadded.length % 4 === 1) return null;
47
+ const decoded = Buffer.from(encoded, 'base64');
48
+ return decoded.length > 0 &&
49
+ decoded.toString('base64').replace(/=+$/, '') === unpadded
50
+ ? decoded
51
+ : null;
52
+ }
53
+
54
+ function signatureMatches(expected: Buffer, supplied: string): boolean {
55
+ const candidates = supplied.trim().split(/\s+/);
56
+ for (const candidate of candidates) {
57
+ const [version, encoded, ...extra] = candidate.split(',');
58
+ if (version !== 'v1' || !encoded || extra.length > 0) continue;
59
+ if (!/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) continue;
60
+ const actual = Buffer.from(encoded, 'base64');
61
+ if (actual.length !== expected.length) continue;
62
+ if (timingSafeEqual(expected, actual)) return true;
63
+ }
64
+ return false;
65
+ }
66
+
67
+ /**
68
+ * Verifies the Standard Webhooks v1 symmetric scheme against the unmodified
69
+ * request body. Call this before admitting any Play Run.
70
+ */
71
+ export function verifyStandardWebhook(input: {
72
+ headers: Record<string, string>;
73
+ rawBody: string;
74
+ signingSecrets: readonly string[];
75
+ headerFamily: PlayStandardWebhookHeaderFamily;
76
+ toleranceSeconds?: number;
77
+ now?: Date;
78
+ }): StandardWebhookVerification {
79
+ const names = headerNames(input.headerFamily);
80
+ const headers = normalizedHeaders(input.headers);
81
+ const eventId = headers[names.id]?.trim();
82
+ const timestampHeader = headers[names.timestamp]?.trim();
83
+ const signature = headers[names.signature];
84
+ if (!eventId || !timestampHeader || !signature) {
85
+ return { ok: false, reason: 'missing_headers' };
86
+ }
87
+ // A full stop is the signed-content delimiter, so the ID must be unambiguous.
88
+ if (eventId.includes('.')) return { ok: false, reason: 'invalid_event_id' };
89
+ if (!/^\d+$/.test(timestampHeader)) {
90
+ return { ok: false, reason: 'invalid_timestamp' };
91
+ }
92
+ const timestamp = Number(timestampHeader);
93
+ if (!Number.isSafeInteger(timestamp)) {
94
+ return { ok: false, reason: 'invalid_timestamp' };
95
+ }
96
+ const toleranceSeconds =
97
+ input.toleranceSeconds ?? DEFAULT_STANDARD_WEBHOOK_TOLERANCE_SECONDS;
98
+ const nowSeconds = Math.floor((input.now?.getTime() ?? Date.now()) / 1000);
99
+ if (Math.abs(nowSeconds - timestamp) > toleranceSeconds) {
100
+ return { ok: false, reason: 'timestamp_out_of_tolerance' };
101
+ }
102
+
103
+ const signedContent = `${eventId}.${timestampHeader}.${input.rawBody}`;
104
+ let usableSecret = false;
105
+ for (const secret of input.signingSecrets) {
106
+ const key = decodeSigningSecret(secret);
107
+ if (!key) continue;
108
+ usableSecret = true;
109
+ const expected = createHmac('sha256', key).update(signedContent).digest();
110
+ if (signatureMatches(expected, signature)) {
111
+ return { ok: true, eventId, timestamp };
112
+ }
113
+ }
114
+ return usableSecret
115
+ ? { ok: false, reason: 'invalid_signature' }
116
+ : { ok: false, reason: 'invalid_secret' };
117
+ }
@@ -0,0 +1,388 @@
1
+ import {
2
+ isSerializedPlayDataset,
3
+ trimSerializedPlayDatasetPreview,
4
+ } from './/dataset';
5
+
6
+ const PLAY_STATUS_PREVIEW_LIMIT = 10;
7
+
8
+ export type PlayRunProgressExecution = {
9
+ scope: 'batch';
10
+ total: number;
11
+ executed: number;
12
+ reused: number;
13
+ skipped: number;
14
+ pending: number;
15
+ failed: number;
16
+ retried?: number;
17
+ degraded?: boolean;
18
+ duplicates?: {
19
+ exact?: number;
20
+ semantic?: number;
21
+ rejected?: number;
22
+ };
23
+ };
24
+
25
+ export type PlayRunProgressExecutionLocation = PlayRunProgressExecution & {
26
+ path: string;
27
+ tableNamespace?: string;
28
+ datasetKind?: string;
29
+ };
30
+
31
+ function isRecord(value: unknown): value is Record<string, unknown> {
32
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
33
+ }
34
+
35
+ function finiteNumber(value: unknown): number | null {
36
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
37
+ }
38
+
39
+ function isDatasetEnvelope(value: Record<string, unknown>): boolean {
40
+ return (
41
+ value.kind === 'dataset' &&
42
+ typeof value.datasetKind === 'string' &&
43
+ typeof value.count === 'number' &&
44
+ Array.isArray(value.preview)
45
+ );
46
+ }
47
+
48
+ function normalizeProgressExecution(
49
+ value: unknown,
50
+ ): PlayRunProgressExecution | null {
51
+ if (!isRecord(value)) {
52
+ return null;
53
+ }
54
+ const total = finiteNumber(value.total);
55
+ const executed = finiteNumber(value.executed);
56
+ const reused = finiteNumber(value.reused);
57
+ const skipped = finiteNumber(value.skipped);
58
+ const pending = finiteNumber(value.pending);
59
+ const failed = finiteNumber(value.failed);
60
+ if (
61
+ total === null ||
62
+ executed === null ||
63
+ reused === null ||
64
+ skipped === null ||
65
+ pending === null ||
66
+ failed === null
67
+ ) {
68
+ return null;
69
+ }
70
+
71
+ const duplicates = isRecord(value.duplicates)
72
+ ? {
73
+ ...(finiteNumber(value.duplicates.exact) !== null
74
+ ? { exact: finiteNumber(value.duplicates.exact) as number }
75
+ : {}),
76
+ ...(finiteNumber(value.duplicates.semantic) !== null
77
+ ? { semantic: finiteNumber(value.duplicates.semantic) as number }
78
+ : {}),
79
+ ...(finiteNumber(value.duplicates.rejected) !== null
80
+ ? { rejected: finiteNumber(value.duplicates.rejected) as number }
81
+ : {}),
82
+ }
83
+ : null;
84
+
85
+ return {
86
+ scope: 'batch',
87
+ total,
88
+ executed,
89
+ reused,
90
+ skipped,
91
+ pending,
92
+ failed,
93
+ ...(finiteNumber(value.retried) !== null
94
+ ? { retried: finiteNumber(value.retried) as number }
95
+ : {}),
96
+ ...(typeof value.degraded === 'boolean'
97
+ ? { degraded: value.degraded }
98
+ : {}),
99
+ ...(duplicates && Object.keys(duplicates).length > 0 ? { duplicates } : {}),
100
+ };
101
+ }
102
+
103
+ function normalizeMapStepProgressExecution(
104
+ value: unknown,
105
+ ): PlayRunProgressExecution | null {
106
+ if (!isRecord(value)) {
107
+ return null;
108
+ }
109
+ const total = finiteNumber(value.total);
110
+ const executed = finiteNumber(value.executed);
111
+ const reused = finiteNumber(value.reused);
112
+ const failed = finiteNumber(value.failed);
113
+ if (
114
+ total === null ||
115
+ executed === null ||
116
+ reused === null ||
117
+ failed === null
118
+ ) {
119
+ return null;
120
+ }
121
+ const skipped = finiteNumber(value.skipped) ?? 0;
122
+ const pending =
123
+ finiteNumber(value.pending) ??
124
+ Math.max(0, total - executed - reused - skipped - failed);
125
+ const duplicates = isRecord(value.duplicates)
126
+ ? {
127
+ ...(finiteNumber(value.duplicates.exact) !== null
128
+ ? { exact: finiteNumber(value.duplicates.exact) as number }
129
+ : {}),
130
+ ...(finiteNumber(value.duplicates.semantic) !== null
131
+ ? { semantic: finiteNumber(value.duplicates.semantic) as number }
132
+ : {}),
133
+ ...(finiteNumber(value.duplicates.rejected) !== null
134
+ ? { rejected: finiteNumber(value.duplicates.rejected) as number }
135
+ : {}),
136
+ }
137
+ : null;
138
+
139
+ return {
140
+ scope: 'batch',
141
+ total,
142
+ executed,
143
+ reused,
144
+ skipped,
145
+ pending,
146
+ failed,
147
+ ...(finiteNumber(value.retried) !== null
148
+ ? { retried: finiteNumber(value.retried) as number }
149
+ : {}),
150
+ ...(typeof value.degraded === 'boolean'
151
+ ? { degraded: value.degraded }
152
+ : {}),
153
+ ...(duplicates && Object.keys(duplicates).length > 0 ? { duplicates } : {}),
154
+ };
155
+ }
156
+
157
+ export function extractRunPackageProgressExecution(
158
+ value: unknown,
159
+ ): PlayRunProgressExecution | null {
160
+ const record = isRecord(value) ? value : null;
161
+ const steps = Array.isArray(record?.steps) ? record.steps : [];
162
+ for (const step of steps) {
163
+ if (!isRecord(step) || step.kind !== 'dataset') {
164
+ continue;
165
+ }
166
+ const progress = normalizeMapStepProgressExecution(step.progress);
167
+ if (progress) {
168
+ return progress;
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+
174
+ export function extractRunProgressExecution(
175
+ value: unknown,
176
+ ): PlayRunProgressExecution | null {
177
+ const walk = (
178
+ candidate: unknown,
179
+ depth: number,
180
+ ): PlayRunProgressExecution | null => {
181
+ if (depth > 8 || candidate == null) {
182
+ return null;
183
+ }
184
+ if (Array.isArray(candidate)) {
185
+ for (const item of candidate) {
186
+ const found = walk(item, depth + 1);
187
+ if (found) return found;
188
+ }
189
+ return null;
190
+ }
191
+ if (!isRecord(candidate)) {
192
+ return null;
193
+ }
194
+ const metadata = isRecord(candidate._metadata) ? candidate._metadata : null;
195
+ const fromDataset = isDatasetEnvelope(candidate)
196
+ ? normalizeProgressExecution(metadata?.workProgress)
197
+ : null;
198
+ if (fromDataset) {
199
+ return fromDataset;
200
+ }
201
+ const fromMetadata = normalizeProgressExecution(metadata?.workProgress);
202
+ if (fromMetadata) {
203
+ return fromMetadata;
204
+ }
205
+ for (const [key, child] of Object.entries(candidate)) {
206
+ if (key === 'preview') continue;
207
+ const found = walk(child, depth + 1);
208
+ if (found) return found;
209
+ }
210
+ return null;
211
+ };
212
+ return walk(value, 0);
213
+ }
214
+
215
+ export function collectRunProgressExecutions(
216
+ value: unknown,
217
+ ): PlayRunProgressExecutionLocation[] {
218
+ const found: PlayRunProgressExecutionLocation[] = [];
219
+ const seen = new Set<string>();
220
+ const push = (
221
+ execution: PlayRunProgressExecution | null,
222
+ candidate: Record<string, unknown>,
223
+ path: string,
224
+ ) => {
225
+ if (!execution) {
226
+ return;
227
+ }
228
+ const tableNamespace =
229
+ typeof candidate.tableNamespace === 'string' &&
230
+ candidate.tableNamespace.trim()
231
+ ? candidate.tableNamespace.trim()
232
+ : undefined;
233
+ const datasetKind =
234
+ typeof candidate.datasetKind === 'string' && candidate.datasetKind.trim()
235
+ ? candidate.datasetKind.trim()
236
+ : undefined;
237
+ const key = [path, tableNamespace ?? '', datasetKind ?? ''].join('\u0000');
238
+ if (seen.has(key)) {
239
+ return;
240
+ }
241
+ seen.add(key);
242
+ found.push({
243
+ ...execution,
244
+ path,
245
+ ...(tableNamespace ? { tableNamespace } : {}),
246
+ ...(datasetKind ? { datasetKind } : {}),
247
+ });
248
+ };
249
+
250
+ const walk = (candidate: unknown, path: string, depth: number) => {
251
+ if (depth > 8 || candidate == null) {
252
+ return;
253
+ }
254
+ if (Array.isArray(candidate)) {
255
+ candidate.forEach((item, index) =>
256
+ walk(item, `${path}[${index}]`, depth + 1),
257
+ );
258
+ return;
259
+ }
260
+ if (!isRecord(candidate)) {
261
+ return;
262
+ }
263
+
264
+ const metadata = isRecord(candidate._metadata) ? candidate._metadata : null;
265
+ push(normalizeProgressExecution(metadata?.workProgress), candidate, path);
266
+
267
+ for (const [key, child] of Object.entries(candidate)) {
268
+ if (key === 'preview') continue;
269
+ walk(child, path ? `${path}.${key}` : key, depth + 1);
270
+ }
271
+ };
272
+
273
+ walk(value, 'result', 0);
274
+ return found;
275
+ }
276
+
277
+ export function stripRunProgressExecution(value: unknown): unknown {
278
+ if (Array.isArray(value)) {
279
+ return value.map(stripRunProgressExecution);
280
+ }
281
+ if (!isRecord(value)) {
282
+ return value;
283
+ }
284
+ const out: Record<string, unknown> = {};
285
+ const datasetEnvelope = isDatasetEnvelope(value);
286
+ for (const [key, child] of Object.entries(value)) {
287
+ if (key === '_metadata' && isRecord(child)) {
288
+ const metadata: Record<string, unknown> = {};
289
+ for (const [metadataKey, metadataValue] of Object.entries(child)) {
290
+ if (metadataKey === 'workProgress') {
291
+ continue;
292
+ }
293
+ metadata[metadataKey] = stripRunProgressExecution(metadataValue);
294
+ }
295
+ if (Object.keys(metadata).length > 0) {
296
+ out[key] = metadata;
297
+ }
298
+ continue;
299
+ }
300
+ out[key] = stripRunProgressExecution(child);
301
+ }
302
+ return out;
303
+ }
304
+
305
+ export function mergeRunProgressExecution(
306
+ progress: Record<string, unknown>,
307
+ execution: PlayRunProgressExecution | null,
308
+ ): Record<string, unknown> {
309
+ if (!execution) {
310
+ return progress;
311
+ }
312
+ return {
313
+ ...progress,
314
+ total: execution.total,
315
+ executed: execution.executed,
316
+ reused: execution.reused,
317
+ skipped: execution.skipped,
318
+ pending: execution.pending,
319
+ failed: execution.failed,
320
+ ...(execution.retried !== undefined ? { retried: execution.retried } : {}),
321
+ ...(execution.degraded !== undefined
322
+ ? { degraded: execution.degraded }
323
+ : {}),
324
+ ...(execution.duplicates ? { duplicates: execution.duplicates } : {}),
325
+ };
326
+ }
327
+
328
+ export function normalizePlayResult(
329
+ value: unknown,
330
+ ): Record<string, unknown> | null {
331
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
332
+ return null;
333
+ }
334
+
335
+ const result = { ...(value as Record<string, unknown>) };
336
+ if (
337
+ result.contract &&
338
+ typeof result.contract === 'object' &&
339
+ !Array.isArray(result.contract)
340
+ ) {
341
+ const contract = {
342
+ ...(result.contract as Record<string, unknown>),
343
+ };
344
+ delete contract.sourceCode;
345
+ result.contract = contract;
346
+ }
347
+ if (isSerializedPlayDataset(result.rows)) {
348
+ result.rows = trimSerializedPlayDatasetPreview(
349
+ result.rows,
350
+ PLAY_STATUS_PREVIEW_LIMIT,
351
+ );
352
+ }
353
+ if (isSerializedPlayDataset(result.results)) {
354
+ result.results = trimSerializedPlayDatasetPreview(
355
+ result.results,
356
+ PLAY_STATUS_PREVIEW_LIMIT,
357
+ );
358
+ }
359
+ if (isSerializedPlayDataset(result.leads)) {
360
+ result.leads = trimSerializedPlayDatasetPreview(
361
+ result.leads,
362
+ PLAY_STATUS_PREVIEW_LIMIT,
363
+ );
364
+ }
365
+ if (
366
+ result.output &&
367
+ typeof result.output === 'object' &&
368
+ !Array.isArray(result.output)
369
+ ) {
370
+ const output = {
371
+ ...(result.output as Record<string, unknown>),
372
+ };
373
+ if (isSerializedPlayDataset(output.rows)) {
374
+ output.rows = trimSerializedPlayDatasetPreview(
375
+ output.rows,
376
+ PLAY_STATUS_PREVIEW_LIMIT,
377
+ );
378
+ }
379
+ if (isSerializedPlayDataset(output.results)) {
380
+ output.results = trimSerializedPlayDatasetPreview(
381
+ output.results,
382
+ PLAY_STATUS_PREVIEW_LIMIT,
383
+ );
384
+ }
385
+ result.output = output;
386
+ }
387
+ return result;
388
+ }