deepline 0.1.288 → 0.1.290
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 +17 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +65 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +167 -8
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/legacy-webhook-drain.ts +110 -0
- package/dist/bundling-sources/shared_libs/plays/secret-guardrails.ts +10 -0
- package/dist/cli/index.js +48 -4
- package/dist/cli/index.mjs +48 -4
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -1
- package/dist/index.mjs +12 -1
- package/dist/plays/bundle-play-file.mjs +5 -0
- package/package.json +1 -1
|
@@ -443,6 +443,8 @@ export type RunsListOptions = {
|
|
|
443
443
|
play?: string;
|
|
444
444
|
status?: string;
|
|
445
445
|
limit?: number;
|
|
446
|
+
/** Zero-based page offset. Requires `play` because status-only inventory is not paginated. */
|
|
447
|
+
offset?: number;
|
|
446
448
|
};
|
|
447
449
|
|
|
448
450
|
/** Options for `client.runs.get(...)`. */
|
|
@@ -2952,6 +2954,21 @@ export class DeeplineClient {
|
|
|
2952
2954
|
if (typeof options.limit === 'number' && Number.isFinite(options.limit)) {
|
|
2953
2955
|
params.set('limit', String(Math.max(1, Math.floor(options.limit))));
|
|
2954
2956
|
}
|
|
2957
|
+
if (options.offset !== undefined) {
|
|
2958
|
+
if (
|
|
2959
|
+
!Number.isFinite(options.offset) ||
|
|
2960
|
+
!Number.isInteger(options.offset) ||
|
|
2961
|
+
options.offset < 0
|
|
2962
|
+
) {
|
|
2963
|
+
throw new Error(
|
|
2964
|
+
'runs.list options.offset must be a non-negative integer.',
|
|
2965
|
+
);
|
|
2966
|
+
}
|
|
2967
|
+
if (!playName && options.offset > 0) {
|
|
2968
|
+
throw new Error('runs.list options.offset requires options.play.');
|
|
2969
|
+
}
|
|
2970
|
+
params.set('offset', String(options.offset));
|
|
2971
|
+
}
|
|
2955
2972
|
if (!playName && !status) {
|
|
2956
2973
|
throw new Error('runs.list requires options.play or options.status.');
|
|
2957
2974
|
}
|
|
@@ -155,7 +155,7 @@ export const SDK_RELEASE = {
|
|
|
155
155
|
// 0.1.253 makes play-page browser opening opt-in and retires --no-open.
|
|
156
156
|
// 0.1.254 removes the internal operations tree from the published SDK CLI.
|
|
157
157
|
// Operators use the checkout-local deepline-admin binary instead.
|
|
158
|
-
version: '0.1.
|
|
158
|
+
version: '0.1.290',
|
|
159
159
|
contracts: {
|
|
160
160
|
api: {
|
|
161
161
|
name: 'sdk-http-api',
|
package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts
CHANGED
|
@@ -27,7 +27,8 @@ const DAYTONA_NETWORK_ALLOW_LIST_ENV = 'DEEPLINE_DAYTONA_NETWORK_ALLOW_LIST';
|
|
|
27
27
|
|
|
28
28
|
export const DAYTONA_CANCELLED_ERROR = 'Daytona play runner cancelled';
|
|
29
29
|
|
|
30
|
-
export type DaytonaClient = Pick<Daytona, 'create'
|
|
30
|
+
export type DaytonaClient = Pick<Daytona, 'create'> &
|
|
31
|
+
Partial<Pick<Daytona, 'get'>>;
|
|
31
32
|
export type DaytonaSandbox = Awaited<ReturnType<DaytonaClient['create']>>;
|
|
32
33
|
export type DaytonaExecutionContext = PlayRunnerExecutionConfig['context'];
|
|
33
34
|
export type DaytonaStageEmitter = (
|
|
@@ -37,6 +38,7 @@ export type DaytonaStageEmitter = (
|
|
|
37
38
|
|
|
38
39
|
export type AcquiredDaytonaSandbox = {
|
|
39
40
|
sandbox: DaytonaSandbox;
|
|
41
|
+
daytonaOrganizationId: string;
|
|
40
42
|
billingStartedAt: number;
|
|
41
43
|
billingEndedAt?: number;
|
|
42
44
|
};
|
|
@@ -56,6 +58,22 @@ type DaytonaCreateResult = {
|
|
|
56
58
|
attemptElapsedMs: number;
|
|
57
59
|
};
|
|
58
60
|
|
|
61
|
+
async function rejectAcquiredSandbox(
|
|
62
|
+
sandbox: DaytonaSandbox,
|
|
63
|
+
reason: string,
|
|
64
|
+
): Promise<never> {
|
|
65
|
+
try {
|
|
66
|
+
await sandbox.delete(30);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const cleanupError = error instanceof Error ? error.message : String(error);
|
|
69
|
+
throw new Error(
|
|
70
|
+
`${reason} Defensive deletion of Daytona sandbox ${sandbox.id} also failed: ${cleanupError}`,
|
|
71
|
+
{ cause: error },
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
throw new Error(reason);
|
|
75
|
+
}
|
|
76
|
+
|
|
59
77
|
function normalizeLabelValue(value: string | null | undefined): string | null {
|
|
60
78
|
const trimmed = value?.trim();
|
|
61
79
|
return trimmed ? trimmed.slice(0, 63) : null;
|
|
@@ -233,7 +251,15 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
233
251
|
} catch (error) {
|
|
234
252
|
const message = error instanceof Error ? error.message : String(error);
|
|
235
253
|
errors.push(message);
|
|
254
|
+
input.emitStage('create:attempt_failed', {
|
|
255
|
+
attempt,
|
|
256
|
+
elapsedMs: Date.now() - input.startedAt,
|
|
257
|
+
attemptElapsedMs: Date.now() - attemptStartedAt,
|
|
258
|
+
error: message,
|
|
259
|
+
});
|
|
236
260
|
console.warn('[play-runner.daytona.create_attempt_failed]', {
|
|
261
|
+
workflowId: input.context.workflowId ?? null,
|
|
262
|
+
runId: input.context.runId ?? null,
|
|
237
263
|
attempt,
|
|
238
264
|
error: message,
|
|
239
265
|
});
|
|
@@ -265,11 +291,46 @@ async function acquireOneShotDaytonaSandbox(input: {
|
|
|
265
291
|
granted.diskGiB !== DAYTONA_SANDBOX_DISK_GIB ||
|
|
266
292
|
granted.gpu !== DAYTONA_SANDBOX_GPU
|
|
267
293
|
) {
|
|
268
|
-
await
|
|
269
|
-
|
|
294
|
+
await rejectAcquiredSandbox(
|
|
295
|
+
result.sandbox,
|
|
270
296
|
`Daytona sandbox resource boundary mismatch: expected cpu=${DAYTONA_SANDBOX_CPU} memoryGiB=${DAYTONA_SANDBOX_MEMORY_GIB} diskGiB=${DAYTONA_SANDBOX_DISK_GIB} gpu=${DAYTONA_SANDBOX_GPU}, granted cpu=${granted.cpu} memoryGiB=${granted.memoryGiB} diskGiB=${granted.diskGiB} gpu=${granted.gpu}`,
|
|
271
297
|
);
|
|
272
298
|
}
|
|
299
|
+
const configuredOrganizationId =
|
|
300
|
+
process.env.DAYTONA_ORGANIZATION_ID?.trim() || null;
|
|
301
|
+
const observedOrganizationId = result.sandbox.organizationId?.trim() || null;
|
|
302
|
+
if (
|
|
303
|
+
configuredOrganizationId &&
|
|
304
|
+
observedOrganizationId &&
|
|
305
|
+
configuredOrganizationId !== observedOrganizationId
|
|
306
|
+
) {
|
|
307
|
+
await rejectAcquiredSandbox(
|
|
308
|
+
result.sandbox,
|
|
309
|
+
'Daytona sandbox organization routing mismatch. Refusing to run customer code in a sandbox whose observed organization differs from the configured organization.',
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
let lookupOrganizationId: string | null = null;
|
|
313
|
+
if (
|
|
314
|
+
!observedOrganizationId &&
|
|
315
|
+
!configuredOrganizationId &&
|
|
316
|
+
input.daytona.get
|
|
317
|
+
) {
|
|
318
|
+
try {
|
|
319
|
+
const lookedUpSandbox = await input.daytona.get(result.sandbox.id);
|
|
320
|
+
lookupOrganizationId = lookedUpSandbox.organizationId?.trim() || null;
|
|
321
|
+
} catch {
|
|
322
|
+
// The failure below is intentionally about the invariant, not the
|
|
323
|
+
// provider response. The newly created sandbox is still deleted.
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const daytonaOrganizationId =
|
|
327
|
+
observedOrganizationId ?? configuredOrganizationId ?? lookupOrganizationId;
|
|
328
|
+
if (!daytonaOrganizationId) {
|
|
329
|
+
return await rejectAcquiredSandbox(
|
|
330
|
+
result.sandbox,
|
|
331
|
+
'Daytona sandbox organization routing identity is missing. Refusing to run customer code without a durable cleanup routing domain.',
|
|
332
|
+
);
|
|
333
|
+
}
|
|
273
334
|
const billingStartedAt = Date.now();
|
|
274
335
|
const sandbox = result.sandbox;
|
|
275
336
|
input.emitStage('create:done', {
|
|
@@ -281,7 +342,7 @@ async function acquireOneShotDaytonaSandbox(input: {
|
|
|
281
342
|
memoryGiB: granted.memoryGiB,
|
|
282
343
|
diskGiB: granted.diskGiB,
|
|
283
344
|
});
|
|
284
|
-
return { sandbox, billingStartedAt };
|
|
345
|
+
return { sandbox, daytonaOrganizationId, billingStartedAt };
|
|
285
346
|
}
|
|
286
347
|
|
|
287
348
|
export function createOneShotDaytonaSandboxLifecycle(input: {
|
|
@@ -61,6 +61,7 @@ export type StagedDaytonaPayload = {
|
|
|
61
61
|
command: string;
|
|
62
62
|
outputPath: string;
|
|
63
63
|
exitCodePath: string;
|
|
64
|
+
runtimeCompletedPath: string;
|
|
64
65
|
progressEventPath: string;
|
|
65
66
|
};
|
|
66
67
|
|
|
@@ -557,6 +558,7 @@ export async function stageDaytonaRunnerPayload(input: {
|
|
|
557
558
|
command,
|
|
558
559
|
outputPath,
|
|
559
560
|
exitCodePath,
|
|
561
|
+
runtimeCompletedPath,
|
|
560
562
|
progressEventPath,
|
|
561
563
|
};
|
|
562
564
|
}
|
|
@@ -289,27 +289,161 @@ export async function inspectDetachedDaytonaRunner(input: {
|
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
|
|
292
|
+
/** Read the runner's exact customer-code completion fence before terminal GC. */
|
|
293
|
+
export async function readDetachedDaytonaRuntimeCompletion(input: {
|
|
294
|
+
sandboxId: string;
|
|
295
|
+
runtimeCompletedPath: string;
|
|
296
|
+
}): Promise<number | null> {
|
|
297
|
+
try {
|
|
298
|
+
const { clientOptions } = loadDaytonaRequiredConfig();
|
|
299
|
+
const sandbox = (await daytonaSdkClientFactory
|
|
300
|
+
.createFull(clientOptions)
|
|
301
|
+
.get(input.sandboxId)) as DaytonaSandbox;
|
|
302
|
+
const marker = JSON.parse(
|
|
303
|
+
(await sandbox.fs.downloadFile(input.runtimeCompletedPath, 5)).toString(
|
|
304
|
+
'utf-8',
|
|
305
|
+
),
|
|
306
|
+
) as { at?: unknown };
|
|
307
|
+
return typeof marker.at === 'number' && Number.isFinite(marker.at)
|
|
308
|
+
? marker.at
|
|
309
|
+
: null;
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.warn(
|
|
312
|
+
'[play-runner.daytona.runtime_completion_marker_unavailable]',
|
|
313
|
+
{
|
|
314
|
+
sandboxId: input.sandboxId,
|
|
315
|
+
// Path is generated per attempt and contains no customer data.
|
|
316
|
+
runtimeCompletedPath: input.runtimeCompletedPath,
|
|
317
|
+
error: error instanceof Error ? error.message : String(error),
|
|
318
|
+
},
|
|
319
|
+
);
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export type DaytonaSandboxDeleteOutcome =
|
|
325
|
+
| {
|
|
326
|
+
kind: 'deleted' | 'already_absent';
|
|
327
|
+
organizationId: string | null;
|
|
328
|
+
}
|
|
329
|
+
| {
|
|
330
|
+
kind: 'timed_out' | 'rate_limited' | 'failed';
|
|
331
|
+
organizationId: string | null;
|
|
332
|
+
code: string;
|
|
333
|
+
detail: string;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
export async function deleteDaytonaSandboxByIdWithOutcome(input: {
|
|
293
337
|
sandboxId: string;
|
|
294
338
|
timeoutSeconds?: number;
|
|
295
|
-
|
|
339
|
+
expectedOrganizationId?: string | null;
|
|
340
|
+
allowUnscopedAlreadyAbsent?: boolean;
|
|
341
|
+
}): Promise<DaytonaSandboxDeleteOutcome> {
|
|
296
342
|
const sandboxId = input.sandboxId?.trim();
|
|
297
|
-
|
|
343
|
+
const expectedOrganizationId = input.expectedOrganizationId?.trim() || null;
|
|
344
|
+
if (!sandboxId) {
|
|
345
|
+
return {
|
|
346
|
+
kind: 'failed',
|
|
347
|
+
organizationId: expectedOrganizationId,
|
|
348
|
+
code: 'invalid_sandbox_id',
|
|
349
|
+
detail: 'Sandbox ID is required.',
|
|
350
|
+
};
|
|
351
|
+
}
|
|
298
352
|
try {
|
|
299
353
|
const { clientOptions } = loadDaytonaRequiredConfig();
|
|
300
354
|
const daytona = daytonaSdkClientFactory.createFull(clientOptions);
|
|
301
355
|
const sandbox = await daytona.get(sandboxId);
|
|
356
|
+
const observedOrganizationId = sandbox.organizationId?.trim() || null;
|
|
357
|
+
if (
|
|
358
|
+
expectedOrganizationId &&
|
|
359
|
+
observedOrganizationId !== expectedOrganizationId
|
|
360
|
+
) {
|
|
361
|
+
return {
|
|
362
|
+
kind: 'failed',
|
|
363
|
+
organizationId: observedOrganizationId,
|
|
364
|
+
code: 'wrong_routing_domain',
|
|
365
|
+
detail: 'Sandbox belongs to a different Daytona organization.',
|
|
366
|
+
};
|
|
367
|
+
}
|
|
302
368
|
await daytona.delete(sandbox, input.timeoutSeconds ?? 30);
|
|
303
|
-
return
|
|
369
|
+
return {
|
|
370
|
+
kind: 'deleted',
|
|
371
|
+
organizationId: observedOrganizationId,
|
|
372
|
+
};
|
|
304
373
|
} catch (error) {
|
|
374
|
+
const failure = describeDaytonaLookupFailure(error);
|
|
375
|
+
// Cleanup is an idempotent "ensure absent" operation. Daytona returning
|
|
376
|
+
// not-found means another cleanup owner already satisfied the obligation.
|
|
377
|
+
// The durable expected organization came from the sandbox returned by the
|
|
378
|
+
// same organization-scoped credential at creation time. When an explicit
|
|
379
|
+
// worker organization is configured it must still match that evidence;
|
|
380
|
+
// deployments which rely only on the provider-returned organization retain
|
|
381
|
+
// that durable creation-domain proof. Legacy eager cleanup retains its
|
|
382
|
+
// previous unscoped behavior through the explicit compatibility option.
|
|
383
|
+
if (failure.httpStatus === 404) {
|
|
384
|
+
const configuredOrganizationId =
|
|
385
|
+
process.env.DAYTONA_ORGANIZATION_ID?.trim() || null;
|
|
386
|
+
if (
|
|
387
|
+
!input.allowUnscopedAlreadyAbsent &&
|
|
388
|
+
(!expectedOrganizationId ||
|
|
389
|
+
(configuredOrganizationId &&
|
|
390
|
+
configuredOrganizationId !== expectedOrganizationId))
|
|
391
|
+
) {
|
|
392
|
+
return {
|
|
393
|
+
kind: 'failed',
|
|
394
|
+
organizationId: configuredOrganizationId,
|
|
395
|
+
code: expectedOrganizationId
|
|
396
|
+
? 'wrong_routing_domain'
|
|
397
|
+
: 'missing_routing_domain',
|
|
398
|
+
detail:
|
|
399
|
+
'Daytona returned not-found without an exact creation-domain match.',
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
console.info('[play-runner.daytona.reclaim_sandbox_already_absent]', {
|
|
403
|
+
sandboxId,
|
|
404
|
+
});
|
|
405
|
+
return {
|
|
406
|
+
kind: 'already_absent',
|
|
407
|
+
organizationId:
|
|
408
|
+
configuredOrganizationId ?? expectedOrganizationId ?? null,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
305
411
|
console.warn('[play-runner.daytona.reclaim_sandbox_delete_failed]', {
|
|
306
412
|
sandboxId,
|
|
307
|
-
|
|
413
|
+
failure,
|
|
308
414
|
});
|
|
309
|
-
|
|
415
|
+
const timedOut =
|
|
416
|
+
failure.httpStatus === 408 ||
|
|
417
|
+
/(?:timeout|timed out|ETIMEDOUT)/i.test(
|
|
418
|
+
`${failure.errorCode ?? ''} ${failure.detail}`,
|
|
419
|
+
);
|
|
420
|
+
return {
|
|
421
|
+
kind:
|
|
422
|
+
failure.httpStatus === 429
|
|
423
|
+
? 'rate_limited'
|
|
424
|
+
: timedOut
|
|
425
|
+
? 'timed_out'
|
|
426
|
+
: 'failed',
|
|
427
|
+
organizationId: expectedOrganizationId,
|
|
428
|
+
code:
|
|
429
|
+
failure.errorCode ??
|
|
430
|
+
(failure.httpStatus ? `http_${failure.httpStatus}` : 'delete_failed'),
|
|
431
|
+
detail: failure.detail,
|
|
432
|
+
};
|
|
310
433
|
}
|
|
311
434
|
}
|
|
312
435
|
|
|
436
|
+
export async function deleteDaytonaSandboxById(input: {
|
|
437
|
+
sandboxId: string;
|
|
438
|
+
timeoutSeconds?: number;
|
|
439
|
+
}): Promise<boolean> {
|
|
440
|
+
const outcome = await deleteDaytonaSandboxByIdWithOutcome({
|
|
441
|
+
...input,
|
|
442
|
+
allowUnscopedAlreadyAbsent: true,
|
|
443
|
+
});
|
|
444
|
+
return outcome.kind === 'deleted' || outcome.kind === 'already_absent';
|
|
445
|
+
}
|
|
446
|
+
|
|
313
447
|
function formatDaytonaExecutionError(error: unknown): string {
|
|
314
448
|
const message = formatDaytonaError(error);
|
|
315
449
|
return isConfiguredDaytonaRuntimeLimit(error)
|
|
@@ -630,6 +764,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
630
764
|
let cancellationCleanupStarted = false;
|
|
631
765
|
let activeAcquiredResource: {
|
|
632
766
|
sandbox: DaytonaSandbox;
|
|
767
|
+
daytonaOrganizationId: string;
|
|
633
768
|
billingStartedAt: number;
|
|
634
769
|
billingEndedAt?: number;
|
|
635
770
|
} | null = null;
|
|
@@ -639,6 +774,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
639
774
|
const runtimeResourceReportErrors = new Set<unknown>();
|
|
640
775
|
const reportRuntimeResource = async (acquired: {
|
|
641
776
|
sandbox: DaytonaSandbox;
|
|
777
|
+
daytonaOrganizationId: string;
|
|
642
778
|
billingStartedAt: number;
|
|
643
779
|
billingEndedAt?: number;
|
|
644
780
|
}) => {
|
|
@@ -658,6 +794,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
658
794
|
process.env.DEEPLINE_RUNTIME_ENVIRONMENT === 'preview'
|
|
659
795
|
? 'preview'
|
|
660
796
|
: 'production',
|
|
797
|
+
daytonaOrganizationId: acquired.daytonaOrganizationId,
|
|
661
798
|
billingStartedAt: acquired.billingStartedAt,
|
|
662
799
|
billingEndedAt,
|
|
663
800
|
cpu: typeof sandbox.cpu === 'number' ? sandbox.cpu : null,
|
|
@@ -673,6 +810,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
673
810
|
};
|
|
674
811
|
const reportRetiringRuntimeResource = async (acquired: {
|
|
675
812
|
sandbox: DaytonaSandbox;
|
|
813
|
+
daytonaOrganizationId: string;
|
|
676
814
|
billingStartedAt: number;
|
|
677
815
|
billingEndedAt?: number;
|
|
678
816
|
}) => {
|
|
@@ -929,6 +1067,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
929
1067
|
cmdId: start.cmdId,
|
|
930
1068
|
outputPath: stagedPayload.outputPath,
|
|
931
1069
|
exitCodePath: stagedPayload.exitCodePath,
|
|
1070
|
+
runtimeCompletedPath: stagedPayload.runtimeCompletedPath,
|
|
932
1071
|
startedAtMs: Date.now(),
|
|
933
1072
|
ceilingMs: DAYTONA_DETACHED_CEILING_SECONDS * 1_000,
|
|
934
1073
|
},
|
|
@@ -988,8 +1127,28 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
988
1127
|
// Resource persistence belongs to the scheduler control plane. Preserve
|
|
989
1128
|
// its typed capacity/fence errors so Absurd can defer or fence the
|
|
990
1129
|
// attempt; converting them into a runner failure would terminally fail a
|
|
991
|
-
// play that never began executing customer code.
|
|
992
|
-
|
|
1130
|
+
// play that never began executing customer code. The sandbox exists
|
|
1131
|
+
// before that durable callback can succeed, so synchronously delete it
|
|
1132
|
+
// before returning the control-plane error. The old fire-and-forget
|
|
1133
|
+
// cleanup path was skipped by this rethrow and leaked the acquisition.
|
|
1134
|
+
if (runtimeResourceReportErrors.has(error)) {
|
|
1135
|
+
const sandbox = sandboxCleanup.currentSandbox();
|
|
1136
|
+
if (sandbox) {
|
|
1137
|
+
try {
|
|
1138
|
+
await sandbox.delete(30);
|
|
1139
|
+
console.info(
|
|
1140
|
+
'[play-runner.daytona.resource_report_failure_cleanup_done]',
|
|
1141
|
+
{ sandboxId: sandbox.id },
|
|
1142
|
+
);
|
|
1143
|
+
} catch (cleanupError) {
|
|
1144
|
+
throw new AggregateError(
|
|
1145
|
+
[error, cleanupError],
|
|
1146
|
+
`Failed to persist or delete acquired Daytona sandbox ${sandbox.id}.`,
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
throw error;
|
|
1151
|
+
}
|
|
993
1152
|
emitDaytonaStage(callbacks, config.context, 'execute:error', {
|
|
994
1153
|
sandboxId: sandboxCleanup.currentSandbox()?.id ?? null,
|
|
995
1154
|
error: formatDaytonaError(error),
|
|
@@ -13,6 +13,8 @@ export type PlayRunnerRuntimeResource = {
|
|
|
13
13
|
kind: 'daytona_sandbox';
|
|
14
14
|
sandboxId: string;
|
|
15
15
|
daytonaEnvironment?: 'preview' | 'production';
|
|
16
|
+
/** Stable, non-secret provider ownership domain returned by Daytona. */
|
|
17
|
+
daytonaOrganizationId?: string;
|
|
16
18
|
billingStartedAt: number;
|
|
17
19
|
billingEndedAt?: number | null;
|
|
18
20
|
terminalReason?: RuntimeResourceTerminalReason | null;
|
|
@@ -39,6 +39,8 @@ export type PlayExecutionSuspension =
|
|
|
39
39
|
* timeout-wake salvage verification. */
|
|
40
40
|
outputPath: string;
|
|
41
41
|
exitCodePath: string;
|
|
42
|
+
/** Exact customer-code completion fence inside the Daytona sandbox. */
|
|
43
|
+
runtimeCompletedPath?: string;
|
|
42
44
|
startedAtMs: number;
|
|
43
45
|
/** Overall run ceiling; the park timeout. */
|
|
44
46
|
ceilingMs: number;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export const LEGACY_WEBHOOK_RETRY_CODES = [
|
|
2
|
+
'ACTIVE_CONCURRENCY_LIMIT',
|
|
3
|
+
'SCHEDULER_CAPACITY',
|
|
4
|
+
'TRANSIENT_LAUNCH_FAILURE',
|
|
5
|
+
] as const;
|
|
6
|
+
|
|
7
|
+
export type LegacyWebhookRetryCode =
|
|
8
|
+
(typeof LEGACY_WEBHOOK_RETRY_CODES)[number];
|
|
9
|
+
|
|
10
|
+
export const LEGACY_WEBHOOK_QUARANTINE_CODES = [
|
|
11
|
+
'PLAY_DEFINITION_MISSING',
|
|
12
|
+
'PLAY_BINDING_MISSING',
|
|
13
|
+
'PLAY_BINDING_DISABLED',
|
|
14
|
+
'PLAY_REVISION_MISSING',
|
|
15
|
+
'PLAY_REVISION_MISMATCH',
|
|
16
|
+
'ARTIFACT_RUNTIME_INCOMPATIBLE',
|
|
17
|
+
'ARTIFACT_REFERENCE_MISSING',
|
|
18
|
+
'WEBHOOK_PAYLOAD_INVALID',
|
|
19
|
+
'PLAY_PRE_RUN_VALIDATION_FAILED',
|
|
20
|
+
'RUN_IDENTITY_CONFLICT',
|
|
21
|
+
'MAX_DELIVERY_ATTEMPTS_EXCEEDED',
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
export type LegacyWebhookQuarantineCode =
|
|
25
|
+
(typeof LEGACY_WEBHOOK_QUARANTINE_CODES)[number];
|
|
26
|
+
|
|
27
|
+
export function isLegacyWebhookQuarantineCode(
|
|
28
|
+
value: unknown,
|
|
29
|
+
): value is LegacyWebhookQuarantineCode {
|
|
30
|
+
return (
|
|
31
|
+
typeof value === 'string' &&
|
|
32
|
+
(LEGACY_WEBHOOK_QUARANTINE_CODES as readonly string[]).includes(value)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type LegacyWebhookQuarantineFailure =
|
|
37
|
+
| { code: 'PLAY_DEFINITION_MISSING' }
|
|
38
|
+
| { code: 'PLAY_BINDING_MISSING' }
|
|
39
|
+
| { code: 'PLAY_BINDING_DISABLED'; actualStatus: string }
|
|
40
|
+
| { code: 'PLAY_REVISION_MISSING' }
|
|
41
|
+
| {
|
|
42
|
+
code: 'PLAY_REVISION_MISMATCH';
|
|
43
|
+
expectedDefinitionId: string;
|
|
44
|
+
actualDefinitionId: string;
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
code: 'ARTIFACT_RUNTIME_INCOMPATIBLE';
|
|
48
|
+
expectedArtifactKind: 'cjs_node20';
|
|
49
|
+
actualArtifactKind: string;
|
|
50
|
+
}
|
|
51
|
+
| { code: 'ARTIFACT_REFERENCE_MISSING' }
|
|
52
|
+
| {
|
|
53
|
+
code: 'WEBHOOK_PAYLOAD_INVALID';
|
|
54
|
+
reason: 'unsupported_shape';
|
|
55
|
+
}
|
|
56
|
+
| { code: 'PLAY_PRE_RUN_VALIDATION_FAILED' }
|
|
57
|
+
| { code: 'RUN_IDENTITY_CONFLICT' }
|
|
58
|
+
| { code: 'MAX_DELIVERY_ATTEMPTS_EXCEEDED' };
|
|
59
|
+
|
|
60
|
+
export type LegacyWebhookDrainState =
|
|
61
|
+
| { kind: 'pending' }
|
|
62
|
+
| {
|
|
63
|
+
kind: 'leased';
|
|
64
|
+
leaseOwner: string;
|
|
65
|
+
leaseExpiresAt: number;
|
|
66
|
+
attempts: number;
|
|
67
|
+
}
|
|
68
|
+
| {
|
|
69
|
+
kind: 'retry';
|
|
70
|
+
failureCode: LegacyWebhookRetryCode;
|
|
71
|
+
retryAt: number;
|
|
72
|
+
attempts: number;
|
|
73
|
+
error: string;
|
|
74
|
+
}
|
|
75
|
+
| {
|
|
76
|
+
kind: 'quarantined';
|
|
77
|
+
failure: LegacyWebhookQuarantineFailure;
|
|
78
|
+
quarantinedAt: number;
|
|
79
|
+
attempts: number;
|
|
80
|
+
error: string;
|
|
81
|
+
}
|
|
82
|
+
| { kind: 'launched'; launchedAt: number }
|
|
83
|
+
| {
|
|
84
|
+
kind: 'migrated';
|
|
85
|
+
migratedAt: number;
|
|
86
|
+
migratedWebhookEventId: string;
|
|
87
|
+
migratedAbsurdTaskId: string;
|
|
88
|
+
}
|
|
89
|
+
| {
|
|
90
|
+
kind: 'retired';
|
|
91
|
+
retiredAt: number;
|
|
92
|
+
retiredBy: string;
|
|
93
|
+
reason: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type LegacyWebhookDrainOutcome =
|
|
97
|
+
| { kind: 'started' }
|
|
98
|
+
| { kind: 'quarantined'; failure: LegacyWebhookQuarantineFailure }
|
|
99
|
+
| { kind: 'retry'; failureCode: LegacyWebhookRetryCode }
|
|
100
|
+
| { kind: 'lease_lost' };
|
|
101
|
+
|
|
102
|
+
export function isLegacyWebhookPayload(
|
|
103
|
+
value: unknown,
|
|
104
|
+
): value is Record<string, unknown> | unknown[] {
|
|
105
|
+
return Array.isArray(value) || (value !== null && typeof value === 'object');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function assertNever(value: never, context: string): never {
|
|
109
|
+
throw new Error(`${context}: ${JSON.stringify(value)}`);
|
|
110
|
+
}
|
|
@@ -8,6 +8,8 @@ const ASSIGNMENT_SECRET_LITERAL_PATTERN =
|
|
|
8
8
|
const HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
|
|
9
9
|
const UUID_IDENTIFIER_PATTERN =
|
|
10
10
|
/^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
11
|
+
const BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN =
|
|
12
|
+
/^bootstrap-[0-9a-f]{32}(?:\/[a-z0-9][a-z0-9_-]{0,127})?$/i;
|
|
11
13
|
const SECRET_LABEL_PATTERN =
|
|
12
14
|
/(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
|
|
13
15
|
|
|
@@ -27,6 +29,10 @@ function isNonSecretUuidIdentifier(value: string): boolean {
|
|
|
27
29
|
return !SECRET_LABEL_PATTERN.test(label);
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
function isNonSecretBootstrapResourceIdentifier(value: string): boolean {
|
|
33
|
+
return BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN.test(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
/**
|
|
31
37
|
* Returns the inline-secret findings in a string (empty if none). The throwing
|
|
32
38
|
* validator below and the workflows→plays migration validator both call this so
|
|
@@ -49,6 +55,10 @@ export function collectInlineSecretFindings(sourceCode: string): string[] {
|
|
|
49
55
|
// UUID-bearing resource names are structured identifiers, not opaque
|
|
50
56
|
// credentials. Keep secret-looking labels on the conservative path.
|
|
51
57
|
if (isNonSecretUuidIdentifier(literal)) continue;
|
|
58
|
+
// Named CI orgs use a deterministic public `bootstrap-<sha256-prefix>`
|
|
59
|
+
// slug. It is an address, not a credential, and owner-qualified ctx.runPlay
|
|
60
|
+
// references must embed it as a literal for static child resolution.
|
|
61
|
+
if (isNonSecretBootstrapResourceIdentifier(literal)) continue;
|
|
52
62
|
if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
|
|
53
63
|
findings.push('high-entropy string literal');
|
|
54
64
|
break;
|
package/dist/cli/index.js
CHANGED
|
@@ -718,7 +718,7 @@ var SDK_RELEASE = {
|
|
|
718
718
|
// 0.1.253 makes play-page browser opening opt-in and retires --no-open.
|
|
719
719
|
// 0.1.254 removes the internal operations tree from the published SDK CLI.
|
|
720
720
|
// Operators use the checkout-local deepline-admin binary instead.
|
|
721
|
-
version: "0.1.
|
|
721
|
+
version: "0.1.290",
|
|
722
722
|
contracts: {
|
|
723
723
|
api: {
|
|
724
724
|
name: "sdk-http-api",
|
|
@@ -4424,6 +4424,17 @@ var DeeplineClient = class {
|
|
|
4424
4424
|
if (typeof options.limit === "number" && Number.isFinite(options.limit)) {
|
|
4425
4425
|
params.set("limit", String(Math.max(1, Math.floor(options.limit))));
|
|
4426
4426
|
}
|
|
4427
|
+
if (options.offset !== void 0) {
|
|
4428
|
+
if (!Number.isFinite(options.offset) || !Number.isInteger(options.offset) || options.offset < 0) {
|
|
4429
|
+
throw new Error(
|
|
4430
|
+
"runs.list options.offset must be a non-negative integer."
|
|
4431
|
+
);
|
|
4432
|
+
}
|
|
4433
|
+
if (!playName && options.offset > 0) {
|
|
4434
|
+
throw new Error("runs.list options.offset requires options.play.");
|
|
4435
|
+
}
|
|
4436
|
+
params.set("offset", String(options.offset));
|
|
4437
|
+
}
|
|
4427
4438
|
if (!playName && !status) {
|
|
4428
4439
|
throw new Error("runs.list requires options.play or options.status.");
|
|
4429
4440
|
}
|
|
@@ -16754,9 +16765,11 @@ async function handleRunGet(args) {
|
|
|
16754
16765
|
return 0;
|
|
16755
16766
|
}
|
|
16756
16767
|
async function handleRunsList(args) {
|
|
16757
|
-
const usage = "Usage: deepline runs list [--play <play-name>] [--status <status>] [--json]";
|
|
16768
|
+
const usage = "Usage: deepline runs list [--play <play-name>] [--status <status>] [--limit <count>] [--offset <count>] [--json]";
|
|
16758
16769
|
let playName = null;
|
|
16759
16770
|
let statusFilter = null;
|
|
16771
|
+
let limit;
|
|
16772
|
+
let offset;
|
|
16760
16773
|
for (let index = 0; index < args.length; index += 1) {
|
|
16761
16774
|
const arg = args[index];
|
|
16762
16775
|
if ((arg === "--play" || arg === "--name") && args[index + 1]) {
|
|
@@ -16767,6 +16780,24 @@ async function handleRunsList(args) {
|
|
|
16767
16780
|
statusFilter = args[++index].trim().toLowerCase();
|
|
16768
16781
|
continue;
|
|
16769
16782
|
}
|
|
16783
|
+
if (arg === "--limit" || arg === "--offset") {
|
|
16784
|
+
const rawValue = args[index + 1];
|
|
16785
|
+
const parsed = rawValue && /^\d+$/.test(rawValue) ? Number.parseInt(rawValue, 10) : Number.NaN;
|
|
16786
|
+
const valid = Number.isSafeInteger(parsed) && (arg === "--limit" ? parsed > 0 : parsed >= 0);
|
|
16787
|
+
if (!valid) {
|
|
16788
|
+
console.error(
|
|
16789
|
+
`${arg} must be ${arg === "--limit" ? "a positive" : "a non-negative"} integer.`
|
|
16790
|
+
);
|
|
16791
|
+
return 1;
|
|
16792
|
+
}
|
|
16793
|
+
if (arg === "--limit") {
|
|
16794
|
+
limit = parsed;
|
|
16795
|
+
} else {
|
|
16796
|
+
offset = parsed;
|
|
16797
|
+
}
|
|
16798
|
+
index += 1;
|
|
16799
|
+
continue;
|
|
16800
|
+
}
|
|
16770
16801
|
if (arg === "--json" || arg === "--compact") {
|
|
16771
16802
|
continue;
|
|
16772
16803
|
}
|
|
@@ -16775,10 +16806,16 @@ async function handleRunsList(args) {
|
|
|
16775
16806
|
console.error(usage);
|
|
16776
16807
|
return 1;
|
|
16777
16808
|
}
|
|
16809
|
+
if ((offset ?? 0) > 0 && !playName) {
|
|
16810
|
+
console.error("--offset requires --play.");
|
|
16811
|
+
return 1;
|
|
16812
|
+
}
|
|
16778
16813
|
const client2 = new DeeplineClient();
|
|
16779
16814
|
const runs = (await client2.runs.list({
|
|
16780
16815
|
...playName ? { play: playName } : {},
|
|
16781
|
-
...statusFilter ? { status: statusFilter } : {}
|
|
16816
|
+
...statusFilter ? { status: statusFilter } : {},
|
|
16817
|
+
...limit !== void 0 ? { limit } : {},
|
|
16818
|
+
...offset !== void 0 ? { offset } : {}
|
|
16782
16819
|
})).map((run) => ({
|
|
16783
16820
|
runId: run.workflowId,
|
|
16784
16821
|
workflowId: run.workflowId,
|
|
@@ -18335,10 +18372,12 @@ Examples:
|
|
|
18335
18372
|
deepline runs list --play my-play --status failed --compact --json
|
|
18336
18373
|
deepline runs list --status running --compact --json
|
|
18337
18374
|
`
|
|
18338
|
-
).option("--play <name>", "Play name to filter runs").option("--status <status>", "Filter by run status").option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
|
|
18375
|
+
).option("--play <name>", "Play name to filter runs").option("--status <status>", "Filter by run status").option("--limit <count>", "Maximum runs to return").option("--offset <count>", "Zero-based page offset (requires --play)").option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
|
|
18339
18376
|
process.exitCode = await handleRunsList([
|
|
18340
18377
|
...options.play ? ["--play", options.play] : [],
|
|
18341
18378
|
...options.status ? ["--status", options.status] : [],
|
|
18379
|
+
...options.limit ? ["--limit", options.limit] : [],
|
|
18380
|
+
...options.offset ? ["--offset", options.offset] : [],
|
|
18342
18381
|
...options.compact ? ["--compact"] : [],
|
|
18343
18382
|
...options.json ? ["--json"] : []
|
|
18344
18383
|
]);
|
|
@@ -29296,6 +29335,7 @@ var BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
|
|
|
29296
29335
|
var ASSIGNMENT_SECRET_LITERAL_PATTERN = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
|
|
29297
29336
|
var HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
|
|
29298
29337
|
var UUID_IDENTIFIER_PATTERN = /^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
29338
|
+
var BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN = /^bootstrap-[0-9a-f]{32}(?:\/[a-z0-9][a-z0-9_-]{0,127})?$/i;
|
|
29299
29339
|
var SECRET_LABEL_PATTERN = /(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
|
|
29300
29340
|
function shannonEntropy(value) {
|
|
29301
29341
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -29311,6 +29351,9 @@ function isNonSecretUuidIdentifier(value) {
|
|
|
29311
29351
|
const label = match[1] ?? "";
|
|
29312
29352
|
return !SECRET_LABEL_PATTERN.test(label);
|
|
29313
29353
|
}
|
|
29354
|
+
function isNonSecretBootstrapResourceIdentifier(value) {
|
|
29355
|
+
return BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN.test(value);
|
|
29356
|
+
}
|
|
29314
29357
|
function collectInlineSecretFindings(sourceCode) {
|
|
29315
29358
|
const findings = [];
|
|
29316
29359
|
for (const match of sourceCode.matchAll(SECRET_ENV_PATTERN)) {
|
|
@@ -29325,6 +29368,7 @@ function collectInlineSecretFindings(sourceCode) {
|
|
|
29325
29368
|
for (const match of sourceCode.matchAll(HIGH_ENTROPY_LITERAL_PATTERN)) {
|
|
29326
29369
|
const literal = match[1] ?? "";
|
|
29327
29370
|
if (isNonSecretUuidIdentifier(literal)) continue;
|
|
29371
|
+
if (isNonSecretBootstrapResourceIdentifier(literal)) continue;
|
|
29328
29372
|
if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
|
|
29329
29373
|
findings.push("high-entropy string literal");
|
|
29330
29374
|
break;
|
package/dist/cli/index.mjs
CHANGED
|
@@ -703,7 +703,7 @@ var SDK_RELEASE = {
|
|
|
703
703
|
// 0.1.253 makes play-page browser opening opt-in and retires --no-open.
|
|
704
704
|
// 0.1.254 removes the internal operations tree from the published SDK CLI.
|
|
705
705
|
// Operators use the checkout-local deepline-admin binary instead.
|
|
706
|
-
version: "0.1.
|
|
706
|
+
version: "0.1.290",
|
|
707
707
|
contracts: {
|
|
708
708
|
api: {
|
|
709
709
|
name: "sdk-http-api",
|
|
@@ -4409,6 +4409,17 @@ var DeeplineClient = class {
|
|
|
4409
4409
|
if (typeof options.limit === "number" && Number.isFinite(options.limit)) {
|
|
4410
4410
|
params.set("limit", String(Math.max(1, Math.floor(options.limit))));
|
|
4411
4411
|
}
|
|
4412
|
+
if (options.offset !== void 0) {
|
|
4413
|
+
if (!Number.isFinite(options.offset) || !Number.isInteger(options.offset) || options.offset < 0) {
|
|
4414
|
+
throw new Error(
|
|
4415
|
+
"runs.list options.offset must be a non-negative integer."
|
|
4416
|
+
);
|
|
4417
|
+
}
|
|
4418
|
+
if (!playName && options.offset > 0) {
|
|
4419
|
+
throw new Error("runs.list options.offset requires options.play.");
|
|
4420
|
+
}
|
|
4421
|
+
params.set("offset", String(options.offset));
|
|
4422
|
+
}
|
|
4412
4423
|
if (!playName && !status) {
|
|
4413
4424
|
throw new Error("runs.list requires options.play or options.status.");
|
|
4414
4425
|
}
|
|
@@ -16783,9 +16794,11 @@ async function handleRunGet(args) {
|
|
|
16783
16794
|
return 0;
|
|
16784
16795
|
}
|
|
16785
16796
|
async function handleRunsList(args) {
|
|
16786
|
-
const usage = "Usage: deepline runs list [--play <play-name>] [--status <status>] [--json]";
|
|
16797
|
+
const usage = "Usage: deepline runs list [--play <play-name>] [--status <status>] [--limit <count>] [--offset <count>] [--json]";
|
|
16787
16798
|
let playName = null;
|
|
16788
16799
|
let statusFilter = null;
|
|
16800
|
+
let limit;
|
|
16801
|
+
let offset;
|
|
16789
16802
|
for (let index = 0; index < args.length; index += 1) {
|
|
16790
16803
|
const arg = args[index];
|
|
16791
16804
|
if ((arg === "--play" || arg === "--name") && args[index + 1]) {
|
|
@@ -16796,6 +16809,24 @@ async function handleRunsList(args) {
|
|
|
16796
16809
|
statusFilter = args[++index].trim().toLowerCase();
|
|
16797
16810
|
continue;
|
|
16798
16811
|
}
|
|
16812
|
+
if (arg === "--limit" || arg === "--offset") {
|
|
16813
|
+
const rawValue = args[index + 1];
|
|
16814
|
+
const parsed = rawValue && /^\d+$/.test(rawValue) ? Number.parseInt(rawValue, 10) : Number.NaN;
|
|
16815
|
+
const valid = Number.isSafeInteger(parsed) && (arg === "--limit" ? parsed > 0 : parsed >= 0);
|
|
16816
|
+
if (!valid) {
|
|
16817
|
+
console.error(
|
|
16818
|
+
`${arg} must be ${arg === "--limit" ? "a positive" : "a non-negative"} integer.`
|
|
16819
|
+
);
|
|
16820
|
+
return 1;
|
|
16821
|
+
}
|
|
16822
|
+
if (arg === "--limit") {
|
|
16823
|
+
limit = parsed;
|
|
16824
|
+
} else {
|
|
16825
|
+
offset = parsed;
|
|
16826
|
+
}
|
|
16827
|
+
index += 1;
|
|
16828
|
+
continue;
|
|
16829
|
+
}
|
|
16799
16830
|
if (arg === "--json" || arg === "--compact") {
|
|
16800
16831
|
continue;
|
|
16801
16832
|
}
|
|
@@ -16804,10 +16835,16 @@ async function handleRunsList(args) {
|
|
|
16804
16835
|
console.error(usage);
|
|
16805
16836
|
return 1;
|
|
16806
16837
|
}
|
|
16838
|
+
if ((offset ?? 0) > 0 && !playName) {
|
|
16839
|
+
console.error("--offset requires --play.");
|
|
16840
|
+
return 1;
|
|
16841
|
+
}
|
|
16807
16842
|
const client2 = new DeeplineClient();
|
|
16808
16843
|
const runs = (await client2.runs.list({
|
|
16809
16844
|
...playName ? { play: playName } : {},
|
|
16810
|
-
...statusFilter ? { status: statusFilter } : {}
|
|
16845
|
+
...statusFilter ? { status: statusFilter } : {},
|
|
16846
|
+
...limit !== void 0 ? { limit } : {},
|
|
16847
|
+
...offset !== void 0 ? { offset } : {}
|
|
16811
16848
|
})).map((run) => ({
|
|
16812
16849
|
runId: run.workflowId,
|
|
16813
16850
|
workflowId: run.workflowId,
|
|
@@ -18364,10 +18401,12 @@ Examples:
|
|
|
18364
18401
|
deepline runs list --play my-play --status failed --compact --json
|
|
18365
18402
|
deepline runs list --status running --compact --json
|
|
18366
18403
|
`
|
|
18367
|
-
).option("--play <name>", "Play name to filter runs").option("--status <status>", "Filter by run status").option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
|
|
18404
|
+
).option("--play <name>", "Play name to filter runs").option("--status <status>", "Filter by run status").option("--limit <count>", "Maximum runs to return").option("--offset <count>", "Zero-based page offset (requires --play)").option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
|
|
18368
18405
|
process.exitCode = await handleRunsList([
|
|
18369
18406
|
...options.play ? ["--play", options.play] : [],
|
|
18370
18407
|
...options.status ? ["--status", options.status] : [],
|
|
18408
|
+
...options.limit ? ["--limit", options.limit] : [],
|
|
18409
|
+
...options.offset ? ["--offset", options.offset] : [],
|
|
18371
18410
|
...options.compact ? ["--compact"] : [],
|
|
18372
18411
|
...options.json ? ["--json"] : []
|
|
18373
18412
|
]);
|
|
@@ -29344,6 +29383,7 @@ var BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
|
|
|
29344
29383
|
var ASSIGNMENT_SECRET_LITERAL_PATTERN = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
|
|
29345
29384
|
var HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
|
|
29346
29385
|
var UUID_IDENTIFIER_PATTERN = /^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
29386
|
+
var BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN = /^bootstrap-[0-9a-f]{32}(?:\/[a-z0-9][a-z0-9_-]{0,127})?$/i;
|
|
29347
29387
|
var SECRET_LABEL_PATTERN = /(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
|
|
29348
29388
|
function shannonEntropy(value) {
|
|
29349
29389
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -29359,6 +29399,9 @@ function isNonSecretUuidIdentifier(value) {
|
|
|
29359
29399
|
const label = match[1] ?? "";
|
|
29360
29400
|
return !SECRET_LABEL_PATTERN.test(label);
|
|
29361
29401
|
}
|
|
29402
|
+
function isNonSecretBootstrapResourceIdentifier(value) {
|
|
29403
|
+
return BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN.test(value);
|
|
29404
|
+
}
|
|
29362
29405
|
function collectInlineSecretFindings(sourceCode) {
|
|
29363
29406
|
const findings = [];
|
|
29364
29407
|
for (const match of sourceCode.matchAll(SECRET_ENV_PATTERN)) {
|
|
@@ -29373,6 +29416,7 @@ function collectInlineSecretFindings(sourceCode) {
|
|
|
29373
29416
|
for (const match of sourceCode.matchAll(HIGH_ENTROPY_LITERAL_PATTERN)) {
|
|
29374
29417
|
const literal = match[1] ?? "";
|
|
29375
29418
|
if (isNonSecretUuidIdentifier(literal)) continue;
|
|
29419
|
+
if (isNonSecretBootstrapResourceIdentifier(literal)) continue;
|
|
29376
29420
|
if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
|
|
29377
29421
|
findings.push("high-entropy string literal");
|
|
29378
29422
|
break;
|
package/dist/index.d.mts
CHANGED
|
@@ -1717,6 +1717,8 @@ type RunsListOptions = {
|
|
|
1717
1717
|
play?: string;
|
|
1718
1718
|
status?: string;
|
|
1719
1719
|
limit?: number;
|
|
1720
|
+
/** Zero-based page offset. Requires `play` because status-only inventory is not paginated. */
|
|
1721
|
+
offset?: number;
|
|
1720
1722
|
};
|
|
1721
1723
|
/** Options for `client.runs.get(...)`. */
|
|
1722
1724
|
type RunsGetOptions = {
|
package/dist/index.d.ts
CHANGED
|
@@ -1717,6 +1717,8 @@ type RunsListOptions = {
|
|
|
1717
1717
|
play?: string;
|
|
1718
1718
|
status?: string;
|
|
1719
1719
|
limit?: number;
|
|
1720
|
+
/** Zero-based page offset. Requires `play` because status-only inventory is not paginated. */
|
|
1721
|
+
offset?: number;
|
|
1720
1722
|
};
|
|
1721
1723
|
/** Options for `client.runs.get(...)`. */
|
|
1722
1724
|
type RunsGetOptions = {
|
package/dist/index.js
CHANGED
|
@@ -438,7 +438,7 @@ var SDK_RELEASE = {
|
|
|
438
438
|
// 0.1.253 makes play-page browser opening opt-in and retires --no-open.
|
|
439
439
|
// 0.1.254 removes the internal operations tree from the published SDK CLI.
|
|
440
440
|
// Operators use the checkout-local deepline-admin binary instead.
|
|
441
|
-
version: "0.1.
|
|
441
|
+
version: "0.1.290",
|
|
442
442
|
contracts: {
|
|
443
443
|
api: {
|
|
444
444
|
name: "sdk-http-api",
|
|
@@ -4144,6 +4144,17 @@ var DeeplineClient = class {
|
|
|
4144
4144
|
if (typeof options.limit === "number" && Number.isFinite(options.limit)) {
|
|
4145
4145
|
params.set("limit", String(Math.max(1, Math.floor(options.limit))));
|
|
4146
4146
|
}
|
|
4147
|
+
if (options.offset !== void 0) {
|
|
4148
|
+
if (!Number.isFinite(options.offset) || !Number.isInteger(options.offset) || options.offset < 0) {
|
|
4149
|
+
throw new Error(
|
|
4150
|
+
"runs.list options.offset must be a non-negative integer."
|
|
4151
|
+
);
|
|
4152
|
+
}
|
|
4153
|
+
if (!playName && options.offset > 0) {
|
|
4154
|
+
throw new Error("runs.list options.offset requires options.play.");
|
|
4155
|
+
}
|
|
4156
|
+
params.set("offset", String(options.offset));
|
|
4157
|
+
}
|
|
4147
4158
|
if (!playName && !status) {
|
|
4148
4159
|
throw new Error("runs.list requires options.play or options.status.");
|
|
4149
4160
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -367,7 +367,7 @@ var SDK_RELEASE = {
|
|
|
367
367
|
// 0.1.253 makes play-page browser opening opt-in and retires --no-open.
|
|
368
368
|
// 0.1.254 removes the internal operations tree from the published SDK CLI.
|
|
369
369
|
// Operators use the checkout-local deepline-admin binary instead.
|
|
370
|
-
version: "0.1.
|
|
370
|
+
version: "0.1.290",
|
|
371
371
|
contracts: {
|
|
372
372
|
api: {
|
|
373
373
|
name: "sdk-http-api",
|
|
@@ -4073,6 +4073,17 @@ var DeeplineClient = class {
|
|
|
4073
4073
|
if (typeof options.limit === "number" && Number.isFinite(options.limit)) {
|
|
4074
4074
|
params.set("limit", String(Math.max(1, Math.floor(options.limit))));
|
|
4075
4075
|
}
|
|
4076
|
+
if (options.offset !== void 0) {
|
|
4077
|
+
if (!Number.isFinite(options.offset) || !Number.isInteger(options.offset) || options.offset < 0) {
|
|
4078
|
+
throw new Error(
|
|
4079
|
+
"runs.list options.offset must be a non-negative integer."
|
|
4080
|
+
);
|
|
4081
|
+
}
|
|
4082
|
+
if (!playName && options.offset > 0) {
|
|
4083
|
+
throw new Error("runs.list options.offset requires options.play.");
|
|
4084
|
+
}
|
|
4085
|
+
params.set("offset", String(options.offset));
|
|
4086
|
+
}
|
|
4076
4087
|
if (!playName && !status) {
|
|
4077
4088
|
throw new Error("runs.list requires options.play or options.status.");
|
|
4078
4089
|
}
|
|
@@ -70,6 +70,7 @@ var BEARER_LITERAL_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i;
|
|
|
70
70
|
var ASSIGNMENT_SECRET_LITERAL_PATTERN = /\b(?:api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{12,}['"]/i;
|
|
71
71
|
var HIGH_ENTROPY_LITERAL_PATTERN = /['"]([A-Za-z0-9+/=_-]{32,})['"]/g;
|
|
72
72
|
var UUID_IDENTIFIER_PATTERN = /^((?:[A-Za-z0-9]+[-_])*)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
73
|
+
var BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN = /^bootstrap-[0-9a-f]{32}(?:\/[a-z0-9][a-z0-9_-]{0,127})?$/i;
|
|
73
74
|
var SECRET_LABEL_PATTERN = /(?:^|[-_])(?:api|auth|access|secret|token|key|password|credential|bearer|sk|pk|live)(?:[-_]|$)/i;
|
|
74
75
|
function shannonEntropy(value) {
|
|
75
76
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -85,6 +86,9 @@ function isNonSecretUuidIdentifier(value) {
|
|
|
85
86
|
const label = match[1] ?? "";
|
|
86
87
|
return !SECRET_LABEL_PATTERN.test(label);
|
|
87
88
|
}
|
|
89
|
+
function isNonSecretBootstrapResourceIdentifier(value) {
|
|
90
|
+
return BOOTSTRAP_RESOURCE_IDENTIFIER_PATTERN.test(value);
|
|
91
|
+
}
|
|
88
92
|
function collectInlineSecretFindings(sourceCode) {
|
|
89
93
|
const findings = [];
|
|
90
94
|
for (const match of sourceCode.matchAll(SECRET_ENV_PATTERN)) {
|
|
@@ -99,6 +103,7 @@ function collectInlineSecretFindings(sourceCode) {
|
|
|
99
103
|
for (const match of sourceCode.matchAll(HIGH_ENTROPY_LITERAL_PATTERN)) {
|
|
100
104
|
const literal = match[1] ?? "";
|
|
101
105
|
if (isNonSecretUuidIdentifier(literal)) continue;
|
|
106
|
+
if (isNonSecretBootstrapResourceIdentifier(literal)) continue;
|
|
102
107
|
if (literal.length >= 40 && shannonEntropy(literal) >= 4.2) {
|
|
103
108
|
findings.push("high-entropy string literal");
|
|
104
109
|
break;
|