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,400 @@
1
+ /**
2
+ * Server-side enforcement of the play input contract at run creation.
3
+ *
4
+ * Two distinct guards, both intended to run BEFORE any run/billing artifact is
5
+ * created (see `apps/deepline-api/src/app/api/v2/plays/run/route.ts` `startPlayRunForAuth`):
6
+ *
7
+ * 1. `validateScalarInputAgainstSchema` — validates the scalar `--input`
8
+ * envelope against the play's declared `inputSchema`. Enforces
9
+ * `additionalProperties: false` (unknown keys are rejected), the declared
10
+ * JSON-schema `type` of each declared property, declared string
11
+ * `minLength`/`maxLength` (so `csv: ""` against `minLength:1` is a 400 rather
12
+ * than a paid failed run), and CSV-vs-scalar mutual exclusion (a non-empty
13
+ * `csv` path may not coexist with scalar data fields). This is a HARD
14
+ * CUTOVER: no warn phase, env flag, or compatibility shim. Previously nothing
15
+ * validated scalar input server-side, so `--input '{"foo":"bar"}'` against a
16
+ * play with `additionalProperties: false` silently exported `foo`, and a
17
+ * `csv` alongside scalar fields silently dropped the scalars.
18
+ *
19
+ * SCOPE: scalar envelope keys ONLY. CSV column passthrough is BY DESIGN
20
+ * (source-column preservation). CSV columns never appear in the scalar input
21
+ * object — the scalar envelope for a CSV play declares `csv` (a string path)
22
+ * and `columns` (an alias map), while real CSV header names live at the row
23
+ * level (`csvInput.rowInputSchema`, `additionalProperties: true`). Validating
24
+ * the scalar envelope therefore never touches CSV column names.
25
+ *
26
+ * 2. `assertCsvHasDataRows` — rejects a CSV that has a header row but zero data
27
+ * rows, before the run is created/charged. A header-only CSV otherwise
28
+ * creates a paid failed run (~$0.02).
29
+ *
30
+ * Both helpers return a structured failure compatible with the run route's
31
+ * `PlayRunStartFailure` envelope (`{ status, error, details? }`) instead of
32
+ * throwing, so the route can surface a clean 400 to the client.
33
+ */
34
+
35
+ export type PlayInputContractFailure = {
36
+ status: 400;
37
+ error: string;
38
+ details?: Record<string, unknown>;
39
+ };
40
+
41
+ type DeclaredInputSchema = {
42
+ type?: unknown;
43
+ additionalProperties?: unknown;
44
+ properties?: unknown;
45
+ required?: unknown;
46
+ /**
47
+ * Prebuilt CSV plays carry a top-level `csvInput` descriptor. We read
48
+ * `csvInput.inputField` to learn which declared property is the CSV path
49
+ * (defaults to `"csv"`), so the csv-vs-scalar mutual-exclusion check is driven
50
+ * by the schema rather than a hardcoded key name.
51
+ */
52
+ csvInput?: unknown;
53
+ };
54
+
55
+ /**
56
+ * Declared property names that are CSV-control inputs (not row data): the alias
57
+ * map that remaps source CSV headers onto canonical play fields. These may
58
+ * coexist with the CSV path. Derived structurally below; this constant just
59
+ * names the well-known control key in the prebuilt schema shape.
60
+ */
61
+ const CSV_CONTROL_KEYS = new Set<string>(['columns']);
62
+
63
+ function csvControlKeys(schema: Record<string, unknown>): Set<string> {
64
+ const keys = new Set(CSV_CONTROL_KEYS);
65
+ const csvInput = schema.csvInput;
66
+ if (!isPlainObject(csvInput)) return keys;
67
+ const runLevelControls = csvInput.runLevelControlFields;
68
+ if (!Array.isArray(runLevelControls)) return keys;
69
+ for (const field of runLevelControls) {
70
+ if (typeof field === 'string' && field.trim()) {
71
+ keys.add(field.trim());
72
+ }
73
+ }
74
+ return keys;
75
+ }
76
+
77
+ /**
78
+ * Resolve which declared scalar property is the CSV-path input for this schema.
79
+ * Driven by `csvInput.inputField`; absent that descriptor the schema is not a
80
+ * CSV play and there is no csv key.
81
+ */
82
+ function csvInputFieldName(schema: Record<string, unknown>): string | null {
83
+ const csvInput = schema.csvInput;
84
+ if (!isPlainObject(csvInput)) return null;
85
+ const field = csvInput.inputField;
86
+ return typeof field === 'string' && field.length > 0 ? field : 'csv';
87
+ }
88
+
89
+ /**
90
+ * The JSON-schema `type` keyword we know how to check at the scalar envelope
91
+ * level. Anything outside this set (e.g. union schemas via `anyOf`) is left
92
+ * un-type-checked here; presence/`additionalProperties` is still enforced.
93
+ */
94
+ type SimpleJsonType =
95
+ | 'object'
96
+ | 'array'
97
+ | 'string'
98
+ | 'number'
99
+ | 'integer'
100
+ | 'boolean'
101
+ | 'null';
102
+
103
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
104
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
105
+ }
106
+
107
+ function jsonTypeOf(value: unknown): SimpleJsonType {
108
+ if (value === null) return 'null';
109
+ if (Array.isArray(value)) return 'array';
110
+ const t = typeof value;
111
+ if (t === 'object') return 'object';
112
+ if (t === 'string') return 'string';
113
+ if (t === 'boolean') return 'boolean';
114
+ if (t === 'number') {
115
+ return Number.isInteger(value as number) ? 'integer' : 'number';
116
+ }
117
+ // Functions / undefined / symbol cannot appear in parsed JSON input.
118
+ return 'object';
119
+ }
120
+
121
+ function typeMatches(
122
+ received: SimpleJsonType,
123
+ expected: SimpleJsonType,
124
+ ): boolean {
125
+ if (received === expected) return true;
126
+ // A JSON `integer` value satisfies a declared `number`.
127
+ if (expected === 'number' && received === 'integer') return true;
128
+ // A whole-valued `number` satisfies a declared `integer`.
129
+ if (expected === 'integer' && received === 'number') return true;
130
+ return false;
131
+ }
132
+
133
+ function declaredTypeFor(propertySchema: unknown): SimpleJsonType | null {
134
+ if (!isPlainObject(propertySchema)) return null;
135
+ const declared = propertySchema.type;
136
+ if (typeof declared !== 'string') return null;
137
+ switch (declared) {
138
+ case 'object':
139
+ case 'array':
140
+ case 'string':
141
+ case 'number':
142
+ case 'integer':
143
+ case 'boolean':
144
+ case 'null':
145
+ return declared;
146
+ default:
147
+ return null;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Validate the scalar `--input` envelope against the play's declared
153
+ * `inputSchema`. Returns `null` when the input is acceptable, otherwise a 400
154
+ * failure describing the first violation class encountered (unknown keys, then
155
+ * type mismatches).
156
+ *
157
+ * When `schema` is null/absent, or does not declare `additionalProperties:
158
+ * false` and has no checkable property types, this is a no-op (returns null) —
159
+ * plays without a strict schema are unaffected, per the task contract.
160
+ */
161
+ export function validateScalarInputAgainstSchema(input: {
162
+ scalarInput: Record<string, unknown>;
163
+ schema: DeclaredInputSchema | null | undefined;
164
+ playName?: string | null;
165
+ }): PlayInputContractFailure | null {
166
+ const { scalarInput, schema } = input;
167
+ if (!isPlainObject(schema)) return null;
168
+
169
+ const properties = isPlainObject(schema.properties) ? schema.properties : {};
170
+ const allowedKeys = Object.keys(properties);
171
+ const additionalPropsForbidden = schema.additionalProperties === false;
172
+
173
+ const inputKeys = Object.keys(scalarInput);
174
+
175
+ // 1. additionalProperties: false → reject unknown scalar keys.
176
+ if (additionalPropsForbidden) {
177
+ const unknownKeys = inputKeys.filter((key) => !(key in properties));
178
+ if (unknownKeys.length > 0) {
179
+ const playLabel = input.playName ? ` for play "${input.playName}"` : '';
180
+ return {
181
+ status: 400,
182
+ error: `Unknown input ${
183
+ unknownKeys.length === 1 ? 'key' : 'keys'
184
+ }${playLabel}: ${unknownKeys
185
+ .map((key) => `"${key}"`)
186
+ .join(', ')}. Allowed ${
187
+ allowedKeys.length === 1 ? 'key is' : 'keys are'
188
+ }: ${
189
+ allowedKeys.length > 0
190
+ ? allowedKeys.map((key) => `"${key}"`).join(', ')
191
+ : '(none)'
192
+ }.`,
193
+ details: {
194
+ unknownKeys,
195
+ allowedKeys,
196
+ },
197
+ };
198
+ }
199
+ }
200
+
201
+ // 2. CSV-vs-scalar mutual exclusion. For a CSV-backed play, the scalar
202
+ // envelope either supplies a CSV path (`csv`) OR per-row scalar data fields
203
+ // — never both. Production run 20260611t145724-650 sent a non-empty `csv`
204
+ // AND scalar data fields (linkedin_url, current_domain, contact_full_name)
205
+ // and the run silently used only the CSV. The csv key and the control keys
206
+ // (e.g. `columns`, the header-alias map) may coexist; any OTHER declared
207
+ // property supplied alongside a non-empty csv is a conflicting data field.
208
+ const csvKey = csvInputFieldName(schema);
209
+ if (csvKey && csvKey in properties) {
210
+ const csvControls = csvControlKeys(schema);
211
+ const csvValue = scalarInput[csvKey];
212
+ const csvSupplied =
213
+ typeof csvValue === 'string'
214
+ ? csvValue.length > 0
215
+ : csvValue !== undefined && csvValue !== null;
216
+ if (csvSupplied) {
217
+ const conflictingKeys = inputKeys.filter(
218
+ (key) =>
219
+ key !== csvKey &&
220
+ !csvControls.has(key) &&
221
+ key in properties &&
222
+ scalarInput[key] !== undefined,
223
+ );
224
+ if (conflictingKeys.length > 0) {
225
+ const playLabel = input.playName ? ` for play "${input.playName}"` : '';
226
+ return {
227
+ status: 400,
228
+ error: `Conflicting input${playLabel}: "${csvKey}" and scalar data ${
229
+ conflictingKeys.length === 1 ? 'field' : 'fields'
230
+ } ${conflictingKeys
231
+ .map((key) => `"${key}"`)
232
+ .join(
233
+ ', ',
234
+ )} are mutually exclusive. Put the values in the CSV or remove "${csvKey}".`,
235
+ details: {
236
+ csvKey,
237
+ conflictingKeys,
238
+ },
239
+ };
240
+ }
241
+ }
242
+ }
243
+
244
+ // 3. Type mismatches and string-length constraints on declared properties
245
+ // (e.g. custom_fields: string when the schema declares object; an empty
246
+ // `csv: ""` against a declared minLength:1). Only checks keys the caller
247
+ // actually supplied; required-field enforcement is left to downstream
248
+ // runtime validation (the play's own input contract), matching prior
249
+ // behavior.
250
+ for (const key of inputKeys) {
251
+ if (!(key in properties)) continue; // unknown keys handled above
252
+ const propertySchema = properties[key];
253
+ const expected = declaredTypeFor(propertySchema);
254
+ const value = scalarInput[key];
255
+ // Optional fields explicitly set to null/undefined are not checked here;
256
+ // absence is handled by required-field validation downstream.
257
+ if (value === undefined) continue;
258
+
259
+ if (expected) {
260
+ const received = jsonTypeOf(value);
261
+ if (!typeMatches(received, expected)) {
262
+ const playLabel = input.playName ? ` for play "${input.playName}"` : '';
263
+ return {
264
+ status: 400,
265
+ error: `Invalid type for input field "${key}"${playLabel}: expected ${expected}, received ${received}.`,
266
+ details: {
267
+ field: key,
268
+ expectedType: expected,
269
+ receivedType: received,
270
+ },
271
+ };
272
+ }
273
+ }
274
+
275
+ // String-length constraints. Generalized from the schema — production run
276
+ // 20260611t150001-525 passed `csv: ""` against a declared minLength:1 and
277
+ // created a paid failed run ("no asset binding"). Enforced for any declared
278
+ // string property carrying minLength/maxLength.
279
+ if (typeof value === 'string' && isPlainObject(propertySchema)) {
280
+ const lengthFailure = checkStringLength({
281
+ key,
282
+ value,
283
+ propertySchema,
284
+ playName: input.playName ?? null,
285
+ });
286
+ if (lengthFailure) return lengthFailure;
287
+ }
288
+ }
289
+
290
+ return null;
291
+ }
292
+
293
+ /**
294
+ * Enforce declared `minLength` / `maxLength` for a supplied string value.
295
+ * Returns a 400 failure naming the field and the violated constraint, otherwise
296
+ * null. Only fires when the property schema declares the bound as a number.
297
+ */
298
+ function checkStringLength(input: {
299
+ key: string;
300
+ value: string;
301
+ propertySchema: Record<string, unknown>;
302
+ playName: string | null;
303
+ }): PlayInputContractFailure | null {
304
+ const { key, value, propertySchema, playName } = input;
305
+ const playLabel = playName ? ` for play "${playName}"` : '';
306
+
307
+ const minLength = propertySchema.minLength;
308
+ if (typeof minLength === 'number' && value.length < minLength) {
309
+ return {
310
+ status: 400,
311
+ error:
312
+ minLength === 1
313
+ ? `${key} must be a non-empty string (minLength 1)${playLabel}.`
314
+ : `${key} must be at least ${minLength} characters (minLength ${minLength})${playLabel}; received length ${value.length}.`,
315
+ details: {
316
+ field: key,
317
+ constraint: 'minLength',
318
+ minLength,
319
+ receivedLength: value.length,
320
+ },
321
+ };
322
+ }
323
+
324
+ const maxLength = propertySchema.maxLength;
325
+ if (typeof maxLength === 'number' && value.length > maxLength) {
326
+ return {
327
+ status: 400,
328
+ error: `${key} must be at most ${maxLength} characters (maxLength ${maxLength})${playLabel}; received length ${value.length}.`,
329
+ details: {
330
+ field: key,
331
+ constraint: 'maxLength',
332
+ maxLength,
333
+ receivedLength: value.length,
334
+ },
335
+ };
336
+ }
337
+
338
+ return null;
339
+ }
340
+
341
+ /**
342
+ * Inspect raw CSV bytes and reject a CSV that has a header row but zero data
343
+ * rows. Returns null when the CSV has at least one data row (or when the bytes
344
+ * are not parseable as a header-bearing CSV — that is left to the runtime CSV
345
+ * parser to surface, so this guard is strictly additive and never masks other
346
+ * failures).
347
+ *
348
+ * "Data row" = any non-empty line after the first (header) line. Trailing
349
+ * blank lines and a trailing newline after the header are ignored.
350
+ */
351
+ export function assertCsvHasDataRows(input: {
352
+ csvText: string;
353
+ logicalPath?: string | null;
354
+ }): PlayInputContractFailure | null {
355
+ const { csvText } = input;
356
+ if (typeof csvText !== 'string') return null;
357
+
358
+ // Normalize line endings, then split. A header-only file is one where every
359
+ // line after the first is empty (or there is only a header line).
360
+ const lines = csvText.replace(/\r\n?/g, '\n').split('\n');
361
+ if (lines.length === 0) return null;
362
+
363
+ // No header at all (empty file) — leave to the runtime parser; not our guard.
364
+ const header = lines[0];
365
+ if (!header || header.trim().length === 0) return null;
366
+
367
+ const hasDataRow = lines.slice(1).some((line) => line.trim().length > 0);
368
+
369
+ if (hasDataRow) return null;
370
+
371
+ const pathLabel = input.logicalPath ? ` ("${input.logicalPath}")` : '';
372
+ return {
373
+ status: 400,
374
+ error: `csv has a header row but no data rows${pathLabel}.`,
375
+ details: {
376
+ ...(input.logicalPath ? { logicalPath: input.logicalPath } : {}),
377
+ reason: 'csv_header_only',
378
+ },
379
+ };
380
+ }
381
+
382
+ /**
383
+ * Decode a base64-encoded CSV upload to text for the zero-row backstop. Returns
384
+ * null when the content type is not CSV or the payload cannot be decoded
385
+ * (decoding failure is surfaced by the runtime, not masked here).
386
+ */
387
+ export function decodeCsvUploadText(input: {
388
+ contentType: string;
389
+ contentBase64: string;
390
+ }): string | null {
391
+ const contentType = input.contentType.toLowerCase();
392
+ const isCsv =
393
+ contentType.includes('text/csv') || contentType.includes('application/csv');
394
+ if (!isCsv) return null;
395
+ try {
396
+ return Buffer.from(input.contentBase64, 'base64').toString('utf-8');
397
+ } catch {
398
+ return null;
399
+ }
400
+ }
@@ -0,0 +1,96 @@
1
+ import type { PlayBundleArtifact } from '../artifact-types';
2
+ import {
3
+ normalizePlayContractCompatibility,
4
+ type NormalizedPlayContractCompatibilitySnapshot,
5
+ type PlayRunContractSnapshot,
6
+ } from '../contracts';
7
+ import { stripInlineSourceMapFromArtifact } from '../artifact-transport';
8
+ import type { PlaySandboxRuntimeLimits } from '../sandbox-runtime-limits';
9
+ import type { AdmittedPlayAuthoringContract } from '../authoring-contract';
10
+
11
+ const MAX_INLINE_PLAY_ARTIFACT_BYTES = 1_500_000;
12
+
13
+ export type PlayRunLaunchValidationSnapshot = {
14
+ sourceCode: string;
15
+ staticPipeline: unknown;
16
+ artifactHash: string;
17
+ graphHash: string;
18
+ maxCreditsPerRun: number | null;
19
+ sandboxRuntimeLimits: PlaySandboxRuntimeLimits;
20
+ authoringContract: AdmittedPlayAuthoringContract;
21
+ };
22
+
23
+ export type PreparedPlayRunContractSnapshot = PlayRunContractSnapshot & {
24
+ compatibility: NormalizedPlayContractCompatibilitySnapshot;
25
+ };
26
+
27
+ export function runtimeArtifactForSchedulerInput(
28
+ artifact: PlayBundleArtifact | null | undefined,
29
+ ): PlayBundleArtifact | null {
30
+ if (!artifact) {
31
+ return null;
32
+ }
33
+ const bytes = Buffer.byteLength(JSON.stringify(artifact), 'utf-8');
34
+ if (bytes > MAX_INLINE_PLAY_ARTIFACT_BYTES) {
35
+ console.info('[perf][plays.start_run]', {
36
+ phase: 'runtime_artifact_inline_skipped',
37
+ ms: 0,
38
+ artifactBytes: bytes,
39
+ maxBytes: MAX_INLINE_PLAY_ARTIFACT_BYTES,
40
+ });
41
+ return null;
42
+ }
43
+ // The scheduler prefers this inline artifact to the stored copy. Keep its
44
+ // map so a Daytona runner can map user errors without an extra artifact read;
45
+ // the size gate above already accounts for the complete artifact.
46
+ const compacted = stripInlineSourceMapFromArtifact(artifact);
47
+ console.info('[perf][plays.start_run]', {
48
+ phase: 'runtime_artifact_inline_compacted',
49
+ ms: 0,
50
+ originalArtifactBytes: bytes,
51
+ compactedArtifactBytes: Buffer.byteLength(
52
+ JSON.stringify(compacted),
53
+ 'utf-8',
54
+ ),
55
+ });
56
+ return compacted;
57
+ }
58
+
59
+ export function buildPlayRunContractSnapshot(input: {
60
+ source?: 'published' | 'ad_hoc' | 'draft';
61
+ artifactStorageKey: string;
62
+ artifact: Pick<PlayBundleArtifact, 'compatibility'>;
63
+ validatedSnapshot: PlayRunLaunchValidationSnapshot;
64
+ transportStaticPipeline?: unknown;
65
+ }): PreparedPlayRunContractSnapshot {
66
+ // The runtime backend is launch infrastructure, not an artifact contract.
67
+ // Copy the admitted artifact compatibility so resumed work cannot acquire
68
+ // newer semantics merely because the runtime was redeployed.
69
+ const compatibility = normalizePlayContractCompatibility(
70
+ input.artifact.compatibility,
71
+ );
72
+ const staticPipeline =
73
+ input.transportStaticPipeline ?? input.validatedSnapshot.staticPipeline;
74
+ return {
75
+ source: input.source ?? 'published',
76
+ revisionVersion: null,
77
+ staticPipeline,
78
+ billingLimit:
79
+ typeof input.validatedSnapshot.maxCreditsPerRun === 'number'
80
+ ? { maxCreditsPerRun: input.validatedSnapshot.maxCreditsPerRun }
81
+ : null,
82
+ runtimeLimit: input.validatedSnapshot.sandboxRuntimeLimits,
83
+ sourceCode: input.validatedSnapshot.sourceCode,
84
+ artifactMetadata: {
85
+ storageKey: input.artifactStorageKey,
86
+ artifactHash: input.validatedSnapshot.artifactHash,
87
+ graphHash: input.validatedSnapshot.graphHash,
88
+ },
89
+ codeFormat: 'cjs_module',
90
+ compatibility,
91
+ authoringContract: {
92
+ ...input.validatedSnapshot.authoringContract,
93
+ staticPipeline,
94
+ },
95
+ };
96
+ }