deepline 0.2.1 → 0.2.2
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 +4 -1
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/observability/scheduled-job-errors.ts +95 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +71 -7
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +23 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
|
@@ -744,7 +744,10 @@ export type MonitorUpdateChangeSummary = {
|
|
|
744
744
|
};
|
|
745
745
|
upstream: {
|
|
746
746
|
resource_replaced: boolean;
|
|
747
|
-
strategy:
|
|
747
|
+
strategy:
|
|
748
|
+
| 'unchanged'
|
|
749
|
+
| 'create_then_delete_previous'
|
|
750
|
+
| 'deferred_until_reactivation';
|
|
748
751
|
};
|
|
749
752
|
};
|
|
750
753
|
export type MonitorUpdateResult = {
|
|
@@ -160,7 +160,7 @@ export const SDK_RELEASE = {
|
|
|
160
160
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
161
161
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
162
162
|
// release keeps lazy paging semantics independent of row residency.
|
|
163
|
-
version: '0.2.
|
|
163
|
+
version: '0.2.2',
|
|
164
164
|
contracts: {
|
|
165
165
|
api: {
|
|
166
166
|
name: 'sdk-http-api',
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { redactTelemetryText } from './redaction';
|
|
2
|
+
|
|
3
|
+
const MAX_ERROR_SUMMARY_LENGTH = 900;
|
|
4
|
+
|
|
5
|
+
const SECRET_ASSIGNMENT =
|
|
6
|
+
/\b((?:[A-Za-z][A-Za-z0-9_-]*[_-])?(?:token|secret|password|api[_ -]?key|access[_ -]?key))\b(\s*[:=]\s*)([^\s,;]+)/gi;
|
|
7
|
+
const AUTHORIZATION_VALUE =
|
|
8
|
+
/\bauthorization\b(\s*[:=]\s*)(?:Bearer\s+)?[^\s,;|]+/gi;
|
|
9
|
+
const BEARER_TOKEN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
|
|
10
|
+
const SLACK_WEBHOOK =
|
|
11
|
+
/https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/_-]+/gi;
|
|
12
|
+
const URL_SECRET = /([?&](?:token|secret|key|signature|sig|code)=)[^&\s]+/gi;
|
|
13
|
+
const BARE_CREDENTIAL_PATTERNS = [
|
|
14
|
+
/\bgh[pousr]_[A-Za-z0-9_]{20,}\b/gi,
|
|
15
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/gi,
|
|
16
|
+
/\b(?:xaat|xain)-[A-Za-z0-9_-]{16,}\b/gi,
|
|
17
|
+
/\bsk-ant-(?:api|oat)[A-Za-z0-9_-]{8,}\b/gi,
|
|
18
|
+
/\bxox(?:a|b|p|r|s)-[A-Za-z0-9-]{10,}\b/gi,
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export type ScheduledJobFailure = {
|
|
22
|
+
jobId: string;
|
|
23
|
+
jobName: string;
|
|
24
|
+
scheduler: string;
|
|
25
|
+
error: unknown;
|
|
26
|
+
runUrl?: string | null;
|
|
27
|
+
stage?: string | null;
|
|
28
|
+
attempt?: number | null;
|
|
29
|
+
occurredAt?: string;
|
|
30
|
+
test?: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function sanitizeScheduledJobError(
|
|
34
|
+
error: unknown,
|
|
35
|
+
maxLength = MAX_ERROR_SUMMARY_LENGTH,
|
|
36
|
+
): string {
|
|
37
|
+
const raw =
|
|
38
|
+
error instanceof Error
|
|
39
|
+
? `${error.name}: ${error.message}`
|
|
40
|
+
: typeof error === 'string'
|
|
41
|
+
? error
|
|
42
|
+
: safeJson(error);
|
|
43
|
+
let compact = redactTelemetryText(raw)
|
|
44
|
+
.replace(/\u001b\[[0-9;]*m/g, '')
|
|
45
|
+
.replace(/\r/g, '')
|
|
46
|
+
.split('\n')
|
|
47
|
+
.map((line) => line.trim())
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.join(' | ')
|
|
50
|
+
.replace(
|
|
51
|
+
AUTHORIZATION_VALUE,
|
|
52
|
+
(_match, separator) => `Authorization${separator}[REDACTED]`,
|
|
53
|
+
)
|
|
54
|
+
.replace(BEARER_TOKEN, 'Bearer [REDACTED]')
|
|
55
|
+
.replace(SLACK_WEBHOOK, '[REDACTED_SLACK_WEBHOOK]')
|
|
56
|
+
.replace(SECRET_ASSIGNMENT, (_match, label, separator) => {
|
|
57
|
+
return `${label}${separator}[REDACTED]`;
|
|
58
|
+
})
|
|
59
|
+
.replace(URL_SECRET, '$1[REDACTED]')
|
|
60
|
+
.replace(/\s+/g, ' ')
|
|
61
|
+
.trim();
|
|
62
|
+
for (const pattern of BARE_CREDENTIAL_PATTERNS) {
|
|
63
|
+
compact = compact.replace(pattern, '[REDACTED_CREDENTIAL]');
|
|
64
|
+
}
|
|
65
|
+
if (!compact) return 'No error detail was captured.';
|
|
66
|
+
return compact.length <= maxLength
|
|
67
|
+
? compact
|
|
68
|
+
: `${compact.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function formatScheduledJobFailure(input: ScheduledJobFailure): string {
|
|
72
|
+
const title = input.test
|
|
73
|
+
? '[TEST] Scheduled job failure alert'
|
|
74
|
+
: 'Scheduled job failed';
|
|
75
|
+
return [
|
|
76
|
+
title,
|
|
77
|
+
`Job: ${input.jobName} (${input.jobId})`,
|
|
78
|
+
`Scheduler: ${input.scheduler}`,
|
|
79
|
+
input.stage ? `Stage: ${input.stage}` : null,
|
|
80
|
+
input.attempt ? `Attempt: ${input.attempt}` : null,
|
|
81
|
+
`Error: ${sanitizeScheduledJobError(input.error)}`,
|
|
82
|
+
`Occurred: ${input.occurredAt ?? new Date().toISOString()}`,
|
|
83
|
+
input.runUrl ? `Diagnostics: ${input.runUrl}` : null,
|
|
84
|
+
]
|
|
85
|
+
.filter((line): line is string => Boolean(line))
|
|
86
|
+
.join('\n');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function safeJson(value: unknown): string {
|
|
90
|
+
try {
|
|
91
|
+
return JSON.stringify(value) ?? String(value);
|
|
92
|
+
} catch {
|
|
93
|
+
return String(value);
|
|
94
|
+
}
|
|
95
|
+
}
|
package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS,
|
|
10
10
|
validatePlaySandboxRuntimeLimits,
|
|
11
11
|
} from '@shared_libs/play-runtime/sandbox-runtime-limits';
|
|
12
|
+
import type { PlayRunnerRuntimeLifecycleEvent } from '../types';
|
|
12
13
|
|
|
13
14
|
const DAYTONA_CREATE_TIMEOUT_SECONDS = 10;
|
|
14
15
|
const DAYTONA_CREATE_RETRY_DELAYS_MS = [0, 500, 1_500] as const;
|
|
@@ -41,6 +42,10 @@ export type DaytonaStageEmitter = (
|
|
|
41
42
|
extra?: Record<string, unknown>,
|
|
42
43
|
) => void;
|
|
43
44
|
|
|
45
|
+
type DaytonaCreateCallObserver = (
|
|
46
|
+
event: PlayRunnerRuntimeLifecycleEvent,
|
|
47
|
+
) => Promise<void>;
|
|
48
|
+
|
|
44
49
|
export type AcquiredDaytonaSandbox = {
|
|
45
50
|
sandbox: DaytonaSandbox;
|
|
46
51
|
daytonaOrganizationId: string;
|
|
@@ -104,6 +109,16 @@ type DaytonaCreateResult = {
|
|
|
104
109
|
attemptElapsedMs: number;
|
|
105
110
|
};
|
|
106
111
|
|
|
112
|
+
function daytonaCreateErrorClass(
|
|
113
|
+
error: unknown,
|
|
114
|
+
): 'timeout' | 'capacity' | 'rate_limit' | 'other' {
|
|
115
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
116
|
+
if (/timed out|timeout/i.test(message)) return 'timeout';
|
|
117
|
+
if (/total cpu limit|capacity/i.test(message)) return 'capacity';
|
|
118
|
+
if (/too many requests|rate limit|429/i.test(message)) return 'rate_limit';
|
|
119
|
+
return 'other';
|
|
120
|
+
}
|
|
121
|
+
|
|
107
122
|
async function rejectAcquiredSandbox(
|
|
108
123
|
sandbox: DaytonaSandbox,
|
|
109
124
|
reason: string,
|
|
@@ -307,11 +322,13 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
307
322
|
orgId: string;
|
|
308
323
|
context: DaytonaExecutionContext;
|
|
309
324
|
emitStage: DaytonaStageEmitter;
|
|
325
|
+
observeCreateCall?: DaytonaCreateCallObserver;
|
|
326
|
+
nextProviderAttempt: () => number;
|
|
310
327
|
startedAt: number;
|
|
311
328
|
}): Promise<DaytonaCreateResult> {
|
|
312
329
|
const errors: string[] = [];
|
|
313
330
|
for (const [index, delayMs] of DAYTONA_CREATE_RETRY_DELAYS_MS.entries()) {
|
|
314
|
-
const attempt =
|
|
331
|
+
const attempt = input.nextProviderAttempt();
|
|
315
332
|
const sandboxName = `dl-${crypto.randomUUID()}`;
|
|
316
333
|
if (delayMs > 0) {
|
|
317
334
|
input.emitStage('create:retry', {
|
|
@@ -323,19 +340,26 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
323
340
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
324
341
|
}
|
|
325
342
|
const attemptStartedAt = Date.now();
|
|
343
|
+
await input.observeCreateCall?.({
|
|
344
|
+
type: 'daytona_create_call_started',
|
|
345
|
+
occurredAtMs: attemptStartedAt,
|
|
346
|
+
providerAttempt: attempt,
|
|
347
|
+
});
|
|
348
|
+
let sandbox: DaytonaSandbox;
|
|
326
349
|
try {
|
|
327
|
-
|
|
350
|
+
sandbox = await createOneShotDaytonaSandbox({
|
|
328
351
|
daytona: input.daytona,
|
|
329
352
|
orgId: input.orgId,
|
|
330
353
|
context: input.context,
|
|
331
354
|
sandboxName,
|
|
332
355
|
});
|
|
333
|
-
return {
|
|
334
|
-
sandbox,
|
|
335
|
-
attempt,
|
|
336
|
-
attemptElapsedMs: Date.now() - attemptStartedAt,
|
|
337
|
-
};
|
|
338
356
|
} catch (error) {
|
|
357
|
+
await input.observeCreateCall?.({
|
|
358
|
+
type: 'daytona_create_call_failed',
|
|
359
|
+
occurredAtMs: Date.now(),
|
|
360
|
+
providerAttempt: attempt,
|
|
361
|
+
errorClass: daytonaCreateErrorClass(error),
|
|
362
|
+
});
|
|
339
363
|
const message = error instanceof Error ? error.message : String(error);
|
|
340
364
|
errors.push(message);
|
|
341
365
|
input.emitStage('create:attempt_failed', {
|
|
@@ -366,7 +390,38 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
366
390
|
{ cause: error },
|
|
367
391
|
);
|
|
368
392
|
}
|
|
393
|
+
continue;
|
|
369
394
|
}
|
|
395
|
+
// The outcome must be attributed immediately after Daytona acknowledges
|
|
396
|
+
// creation, before resource-policy validation can reject it. Otherwise a
|
|
397
|
+
// created-but-rejected sandbox is indistinguishable from an unknown
|
|
398
|
+
// create result. Do not turn a failed *post-create* journal write into a
|
|
399
|
+
// new provider create: the sandbox is already real. The missing durable
|
|
400
|
+
// outcome is deliberately loud in worker logs and makes the capture gate
|
|
401
|
+
// fail as incomplete telemetry, while the normal resource ledger still
|
|
402
|
+
// records the known sandbox for cleanup below.
|
|
403
|
+
const acquiredAt = Date.now();
|
|
404
|
+
try {
|
|
405
|
+
await input.observeCreateCall?.({
|
|
406
|
+
type: 'daytona_create_call_succeeded',
|
|
407
|
+
occurredAtMs: acquiredAt,
|
|
408
|
+
providerAttempt: attempt,
|
|
409
|
+
sandboxId: sandbox.id,
|
|
410
|
+
});
|
|
411
|
+
} catch {
|
|
412
|
+
console.error('[play-runner.daytona.create_lifecycle_event_unrecorded]', {
|
|
413
|
+
workflowId: input.context.workflowId ?? null,
|
|
414
|
+
runId: input.context.runId ?? null,
|
|
415
|
+
attempt,
|
|
416
|
+
sandboxId: sandbox.id,
|
|
417
|
+
eventType: 'daytona_create_call_succeeded',
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
sandbox,
|
|
422
|
+
attempt,
|
|
423
|
+
attemptElapsedMs: acquiredAt - attemptStartedAt,
|
|
424
|
+
};
|
|
370
425
|
}
|
|
371
426
|
const message = `Daytona sandbox create failed across ${errors.length} bounded attempts: ${errors.join('; ')}`;
|
|
372
427
|
const fallbackReason =
|
|
@@ -385,6 +440,8 @@ async function acquireOneShotDaytonaSandbox(input: {
|
|
|
385
440
|
orgId: string;
|
|
386
441
|
context: DaytonaExecutionContext;
|
|
387
442
|
emitStage: DaytonaStageEmitter;
|
|
443
|
+
observeCreateCall?: DaytonaCreateCallObserver;
|
|
444
|
+
nextProviderAttempt: () => number;
|
|
388
445
|
startedAt: number;
|
|
389
446
|
}): Promise<AcquiredDaytonaSandbox> {
|
|
390
447
|
const limits = validatePlaySandboxRuntimeLimits(
|
|
@@ -464,11 +521,13 @@ export function createOneShotDaytonaSandboxLifecycle(input: {
|
|
|
464
521
|
daytona: DaytonaClient;
|
|
465
522
|
context: DaytonaExecutionContext;
|
|
466
523
|
emitStage: DaytonaStageEmitter;
|
|
524
|
+
observeCreateCall?: DaytonaCreateCallObserver;
|
|
467
525
|
startedAt?: number;
|
|
468
526
|
}): OneShotDaytonaSandboxLifecycle {
|
|
469
527
|
const orgId = validateDaytonaExecutionContext(input.context);
|
|
470
528
|
const startedAt = input.startedAt ?? Date.now();
|
|
471
529
|
let disposed = false;
|
|
530
|
+
let providerAttempt = 0;
|
|
472
531
|
const acquiredSandboxes = new Map<string, AcquiredDaytonaSandbox>();
|
|
473
532
|
let latestAcquiredSandboxPromise: Promise<AcquiredDaytonaSandbox>;
|
|
474
533
|
const createFreshSandbox = () => {
|
|
@@ -477,6 +536,11 @@ export function createOneShotDaytonaSandboxLifecycle(input: {
|
|
|
477
536
|
orgId,
|
|
478
537
|
context: input.context,
|
|
479
538
|
emitStage: input.emitStage,
|
|
539
|
+
observeCreateCall: input.observeCreateCall,
|
|
540
|
+
nextProviderAttempt: () => {
|
|
541
|
+
providerAttempt += 1;
|
|
542
|
+
return providerAttempt;
|
|
543
|
+
},
|
|
480
544
|
startedAt,
|
|
481
545
|
}).then((acquired) => {
|
|
482
546
|
acquiredSandboxes.set(acquired.sandbox.id, acquired);
|
|
@@ -670,6 +670,7 @@ function prepareDaytonaExecution(
|
|
|
670
670
|
context: input.context,
|
|
671
671
|
emitStage: (stage, extra) =>
|
|
672
672
|
emitDaytonaStage(callbacks, input.context, stage, extra),
|
|
673
|
+
observeCreateCall: callbacks?.onRuntimeLifecycleEvent,
|
|
673
674
|
});
|
|
674
675
|
return {
|
|
675
676
|
kind: 'daytona',
|
|
@@ -40,6 +40,22 @@ export type RuntimeResourceTerminalReason =
|
|
|
40
40
|
| 'cancelled'
|
|
41
41
|
| 'lease_expired';
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Safe, provider-edge lifecycle evidence. The scheduler records these before
|
|
45
|
+
* relying on a non-idempotent provider outcome; raw provider responses,
|
|
46
|
+
* commands, inputs, and credentials are intentionally not part of the type.
|
|
47
|
+
*/
|
|
48
|
+
export type PlayRunnerRuntimeLifecycleEvent = {
|
|
49
|
+
type:
|
|
50
|
+
| 'daytona_create_call_started'
|
|
51
|
+
| 'daytona_create_call_succeeded'
|
|
52
|
+
| 'daytona_create_call_failed';
|
|
53
|
+
occurredAtMs: number;
|
|
54
|
+
providerAttempt: number;
|
|
55
|
+
sandboxId?: string;
|
|
56
|
+
errorClass?: 'timeout' | 'capacity' | 'rate_limit' | 'other';
|
|
57
|
+
};
|
|
58
|
+
|
|
43
59
|
export class RuntimeResourceFenceLostError extends Error {
|
|
44
60
|
constructor(message: string) {
|
|
45
61
|
super(message);
|
|
@@ -55,6 +71,13 @@ export interface PlayRunnerCallbacks {
|
|
|
55
71
|
onRuntimeResourceAcquired?: (
|
|
56
72
|
resource: PlayRunnerRuntimeResource,
|
|
57
73
|
) => void | Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Durable scheduler-owned evidence for a provider create call. Backends
|
|
76
|
+
* await this callback at the create boundary; it is not best-effort logging.
|
|
77
|
+
*/
|
|
78
|
+
onRuntimeLifecycleEvent?: (
|
|
79
|
+
event: PlayRunnerRuntimeLifecycleEvent,
|
|
80
|
+
) => Promise<void>;
|
|
58
81
|
/**
|
|
59
82
|
* Scheduler-owned readiness read for a detached runner attempt. Daytona uses
|
|
60
83
|
* this direct control-plane port before parking; it is never serialized into
|
package/dist/cli/index.js
CHANGED
|
@@ -1040,7 +1040,7 @@ var SDK_RELEASE = {
|
|
|
1040
1040
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
1041
1041
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
1042
1042
|
// release keeps lazy paging semantics independent of row residency.
|
|
1043
|
-
version: "0.2.
|
|
1043
|
+
version: "0.2.2",
|
|
1044
1044
|
contracts: {
|
|
1045
1045
|
api: {
|
|
1046
1046
|
name: "sdk-http-api",
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1025,7 +1025,7 @@ var SDK_RELEASE = {
|
|
|
1025
1025
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
1026
1026
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
1027
1027
|
// release keeps lazy paging semantics independent of row residency.
|
|
1028
|
-
version: "0.2.
|
|
1028
|
+
version: "0.2.2",
|
|
1029
1029
|
contracts: {
|
|
1030
1030
|
api: {
|
|
1031
1031
|
name: "sdk-http-api",
|
package/dist/index.d.mts
CHANGED
|
@@ -1992,7 +1992,7 @@ type MonitorUpdateChangeSummary = {
|
|
|
1992
1992
|
};
|
|
1993
1993
|
upstream: {
|
|
1994
1994
|
resource_replaced: boolean;
|
|
1995
|
-
strategy: 'unchanged' | 'create_then_delete_previous';
|
|
1995
|
+
strategy: 'unchanged' | 'create_then_delete_previous' | 'deferred_until_reactivation';
|
|
1996
1996
|
};
|
|
1997
1997
|
};
|
|
1998
1998
|
type MonitorUpdateResult = {
|
package/dist/index.d.ts
CHANGED
|
@@ -1992,7 +1992,7 @@ type MonitorUpdateChangeSummary = {
|
|
|
1992
1992
|
};
|
|
1993
1993
|
upstream: {
|
|
1994
1994
|
resource_replaced: boolean;
|
|
1995
|
-
strategy: 'unchanged' | 'create_then_delete_previous';
|
|
1995
|
+
strategy: 'unchanged' | 'create_then_delete_previous' | 'deferred_until_reactivation';
|
|
1996
1996
|
};
|
|
1997
1997
|
};
|
|
1998
1998
|
type MonitorUpdateResult = {
|
package/dist/index.js
CHANGED
|
@@ -763,7 +763,7 @@ var SDK_RELEASE = {
|
|
|
763
763
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
764
764
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
765
765
|
// release keeps lazy paging semantics independent of row residency.
|
|
766
|
-
version: "0.2.
|
|
766
|
+
version: "0.2.2",
|
|
767
767
|
contracts: {
|
|
768
768
|
api: {
|
|
769
769
|
name: "sdk-http-api",
|
package/dist/index.mjs
CHANGED
|
@@ -689,7 +689,7 @@ var SDK_RELEASE = {
|
|
|
689
689
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
690
690
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
691
691
|
// release keeps lazy paging semantics independent of row residency.
|
|
692
|
-
version: "0.2.
|
|
692
|
+
version: "0.2.2",
|
|
693
693
|
contracts: {
|
|
694
694
|
api: {
|
|
695
695
|
name: "sdk-http-api",
|