deepline 0.3.313 → 0.3.315
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 +12 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/types.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +50 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-event-query.ts +15 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +210 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +226 -14
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +4 -18
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +94 -14
- package/dist/bundling-sources/shared_libs/plays/docflow-binding-owner.ts +61 -11
- package/dist/cli/{auth-LNA6WCQA.mjs → auth-OVWOO5NP.mjs} +4 -4
- package/dist/cli/auth-P2TTZU36.js +31 -0
- package/dist/cli/{billing-GJJUUCOZ.js → billing-SRZL7UOH.js} +70 -70
- package/dist/cli/{billing-D37QNIE3.mjs → billing-W3OXKWNE.mjs} +4 -4
- package/dist/cli/{chunk-HY2SSFRI.js → chunk-6BJACIVK.js} +1 -1
- package/dist/cli/{chunk-BS4GAUMH.mjs → chunk-AO4IWPP7.mjs} +33 -8
- package/dist/cli/{chunk-DOI3UBGV.mjs → chunk-AZWQJAQF.mjs} +1 -1
- package/dist/cli/{chunk-XFEPRIW7.mjs → chunk-B473QUQU.mjs} +4 -4
- package/dist/cli/{chunk-P4N7233B.js → chunk-C5WSTXUB.js} +247 -222
- package/dist/cli/{chunk-4UXNEC5C.js → chunk-FGAK6FET.js} +26 -26
- package/dist/cli/{chunk-3ZC4PHJ6.mjs → chunk-FTJQO757.mjs} +1 -1
- package/dist/cli/{chunk-UQBMMN4U.mjs → chunk-GK67FYMS.mjs} +5 -5
- package/dist/cli/{chunk-CSH4PKPJ.mjs → chunk-HNKHMHJ7.mjs} +1 -1
- package/dist/cli/{chunk-YZ5PP6TX.mjs → chunk-IGE7EJJY.mjs} +23 -3
- package/dist/cli/{chunk-HM2TSTCT.mjs → chunk-JF4O5OMH.mjs} +1 -1
- package/dist/cli/{chunk-N2DXWS6W.mjs → chunk-L5TSFCJ6.mjs} +2 -2
- package/dist/cli/{chunk-6G7ZYA6E.js → chunk-LOWURQMF.js} +50 -50
- package/dist/cli/{chunk-FJUIXWZF.js → chunk-OJP4FCCX.js} +48 -48
- package/dist/cli/{chunk-BLMO3JMZ.mjs → chunk-OUIIYFG3.mjs} +2 -2
- package/dist/cli/{chunk-MHETTUWR.js → chunk-OYJCNCUW.js} +2 -2
- package/dist/cli/{chunk-JG2ZTDZV.js → chunk-PPONZ6IE.js} +15 -15
- package/dist/cli/{chunk-QCXDLOGA.mjs → chunk-R3IFZDJL.mjs} +2 -2
- package/dist/cli/{chunk-KNGTN7NE.js → chunk-SNFSO2AN.js} +6 -6
- package/dist/cli/{chunk-RDUS2EDE.mjs → chunk-TQ3F77AC.mjs} +3 -3
- package/dist/cli/{chunk-I4FHFGMA.js → chunk-TQK662WG.js} +60 -60
- package/dist/cli/{chunk-OTA5C7DD.mjs → chunk-TZ5KUDPP.mjs} +1 -1
- package/dist/cli/{chunk-FSZTWQFK.js → chunk-URVZGYGG.js} +4 -4
- package/dist/cli/{chunk-VSAYSOVC.js → chunk-VYK23G2C.js} +222 -202
- package/dist/cli/{chunk-GQXEHX5X.js → chunk-Y42UERUY.js} +6 -6
- package/dist/cli/{chunk-QQMKX7JW.js → chunk-YL42TF53.js} +6 -6
- package/dist/cli/client-CXFPFCIG.js +20 -0
- package/dist/cli/{client-6RVRZ4OY.mjs → client-WQ3J5BSH.mjs} +3 -3
- package/dist/cli/{csv-SBOHBKEG.mjs → csv-36ARCBNQ.mjs} +3 -3
- package/dist/cli/{csv-2UHRG67O.js → csv-CENMEWDH.js} +12 -12
- package/dist/cli/{db-VRE7CLIQ.js → db-6DQZG6NB.js} +20 -20
- package/dist/cli/{db-TUFTPYEA.mjs → db-JU7XLKQM.mjs} +4 -4
- package/dist/cli/{describe-WCBZTSOF.mjs → describe-34ALEEFO.mjs} +8 -8
- package/dist/cli/{describe-GS5A2XH4.js → describe-VUD4AFBY.js} +15 -15
- package/dist/cli/{executions-ENMOOKWH.js → executions-H2UXS6FP.js} +6 -6
- package/dist/cli/{executions-UTN3O372.mjs → executions-H66BMN6N.mjs} +4 -4
- package/dist/cli/{feedback-PXBA3AG7.mjs → feedback-Q7LXXLSH.mjs} +3 -3
- package/dist/cli/{feedback-BLN6SIRG.js → feedback-YSYFQSAB.js} +6 -6
- package/dist/cli/git-SYFCYIFP.js +23 -0
- package/dist/cli/{git-YDMIRW3Q.mjs → git-XV43NSWN.mjs} +4 -4
- package/dist/cli/index.js +74 -74
- package/dist/cli/index.mjs +31 -31
- package/dist/cli/{legacy-noops-CWTUWTDB.js → legacy-noops-RGDW6SCA.js} +7 -7
- package/dist/cli/{legacy-noops-OVG2SICQ.mjs → legacy-noops-Z2DTZQN3.mjs} +4 -4
- package/dist/cli/{monitors-T63OPBTY.js → monitors-I75KZ2BP.js} +85 -85
- package/dist/cli/{monitors-ATIDXJA4.mjs → monitors-OFQ56O4S.mjs} +4 -4
- package/dist/cli/{org-7RVAPMRN.js → org-75AH6RIB.js} +60 -60
- package/dist/cli/{org-NXKNIM3H.mjs → org-ZYMCU6XO.mjs} +4 -4
- package/dist/cli/{play-B62OVHD4.mjs → play-BKXM76DV.mjs} +7 -7
- package/dist/cli/play-OKXUWPMO.js +152 -0
- package/dist/cli/{providers-U7B7JEYY.mjs → providers-GWG2WFIY.mjs} +4 -4
- package/dist/cli/{providers-EITVEGXD.js → providers-Z77MM7ID.js} +7 -7
- package/dist/cli/{quickstart-VSWTYNJM.mjs → quickstart-BGVIV374.mjs} +3 -3
- package/dist/cli/{quickstart-G64F2XRU.js → quickstart-I4RECIQC.js} +5 -5
- package/dist/cli/search-3KISUDDT.js +26 -0
- package/dist/cli/{search-SKYTFKKF.mjs → search-TIKBP3RE.mjs} +5 -5
- package/dist/cli/{secrets-JSFHSCGM.js → secrets-SNGRI4KS.js} +13 -13
- package/dist/cli/{secrets-4DNS7KWS.mjs → secrets-YCHKLWKC.mjs} +4 -4
- package/dist/cli/{sessions-HB7RQ4XN.mjs → sessions-IRDG6ZRX.mjs} +4 -4
- package/dist/cli/{sessions-MLTYPPCG.js → sessions-WXLADGDX.js} +5 -5
- package/dist/cli/{settings-3PCKNBPU.js → settings-72J4BVF6.js} +74 -74
- package/dist/cli/{settings-BT5WCU65.mjs → settings-KZALJDXW.mjs} +7 -7
- package/dist/cli/{setup-EIWDTMCK.mjs → setup-4KCA7ESV.mjs} +6 -6
- package/dist/cli/{setup-EYZWTDJI.js → setup-FFB3KLFD.js} +41 -41
- package/dist/cli/{skills-AQ3ORN2C.mjs → skills-MQZPYUQ3.mjs} +4 -4
- package/dist/cli/skills-XGOK4GRW.js +28 -0
- package/dist/cli/tools-LEAJ7EPW.js +38 -0
- package/dist/cli/{tools-OFTB2BGP.mjs → tools-M3WKRZ5T.mjs} +6 -6
- package/dist/cli/{update-ERQTFSAR.mjs → update-QJMNYGNF.mjs} +6 -6
- package/dist/cli/update-ZN6LDUHS.js +28 -0
- package/dist/cli/{workflow-6D6NHUH4.js → workflow-35UUOZ5E.js} +15 -15
- package/dist/cli/{workflow-SRLWGGBM.mjs → workflow-6T2EUFCN.mjs} +5 -5
- package/dist/{client-CtVdLZux.d.mts → client-CvpZsBqt.d.mts} +12 -0
- package/dist/{client-OBwghxPC.d.ts → client-Dh_4Ph1R.d.ts} +12 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -6
- package/dist/index.mjs +3 -3
- package/dist/install-integrity.json +76 -76
- package/dist/plays/run-request.d.mts +1 -1
- package/dist/plays/run-request.d.ts +1 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
- package/dist/cli/auth-56NYRTQK.js +0 -31
- package/dist/cli/client-XUTMYJDL.js +0 -20
- package/dist/cli/git-26D3VD2I.js +0 -23
- package/dist/cli/play-WM3NZVE7.js +0 -152
- package/dist/cli/search-TXQJJQWY.js +0 -26
- package/dist/cli/skills-73T4BSDE.js +0 -28
- package/dist/cli/tools-ZGSOHWBY.js +0 -38
- package/dist/cli/update-L4V5TF7I.js +0 -28
package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { CreateSandboxFromSnapshotParams, Sandbox } from '@daytonaio/sdk';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
boundedDaytonaProviderTextForLog,
|
|
4
|
+
type DaytonaLaunchOutcomeDiagnostic,
|
|
5
|
+
DaytonaLaunchOutcomeUnknownError,
|
|
6
|
+
daytonaErrorClassForLog,
|
|
7
|
+
daytonaErrorCodeForLog,
|
|
8
|
+
daytonaErrorStatusForLog,
|
|
9
|
+
daytonaSandboxStateForLog,
|
|
10
|
+
} from './daytona-launch-transport';
|
|
3
11
|
import type {
|
|
4
12
|
PlayRunnerExecutionConfig,
|
|
5
13
|
PlayRunnerResult,
|
|
@@ -110,7 +118,15 @@ export class DaytonaSandboxAcquisitionUnavailableError extends Error {
|
|
|
110
118
|
* creating another sandbox could execute the Play twice.
|
|
111
119
|
*/
|
|
112
120
|
export class DaytonaSandboxTransitionPendingError extends Error {
|
|
113
|
-
constructor(
|
|
121
|
+
constructor(
|
|
122
|
+
readonly sandboxName: string,
|
|
123
|
+
/**
|
|
124
|
+
* What Daytona reported when this decision was made. Log-only: a 404, a
|
|
125
|
+
* lookup error, and every non-started state all reach this same error, so
|
|
126
|
+
* the scheduler logs this to tell them apart. It never changes recovery.
|
|
127
|
+
*/
|
|
128
|
+
readonly recoveryLogDetail: DaytonaSandboxTransitionPendingLogDetail | null = null,
|
|
129
|
+
) {
|
|
114
130
|
super(
|
|
115
131
|
`Daytona sandbox ${sandboxName} is still transitioning after create timed out.`,
|
|
116
132
|
);
|
|
@@ -118,6 +134,140 @@ export class DaytonaSandboxTransitionPendingError extends Error {
|
|
|
118
134
|
}
|
|
119
135
|
}
|
|
120
136
|
|
|
137
|
+
/** One provider lookup of the named sandbox, reduced to bounded log facts. */
|
|
138
|
+
type DaytonaNamedSandboxObservation = {
|
|
139
|
+
lookup: 'found' | 'not_found' | 'error' | 'unsupported';
|
|
140
|
+
sandboxState: string | null;
|
|
141
|
+
/** Not `sandboxId`: that key means an acquired sandbox to lifecycle tooling. */
|
|
142
|
+
observedSandboxId: string | null;
|
|
143
|
+
providerErrorReason: string | null;
|
|
144
|
+
lookupErrorClass: string | null;
|
|
145
|
+
lookupErrno: string | null;
|
|
146
|
+
lookupErrorStatus: number | null;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Flat, bounded scalars only: enumerated states and classes, one provider
|
|
151
|
+
* sandbox id, counts, durations, and one capped provider text field.
|
|
152
|
+
*/
|
|
153
|
+
export type DaytonaSandboxTransitionPendingLogDetail =
|
|
154
|
+
DaytonaNamedSandboxObservation & {
|
|
155
|
+
phase: 'create_reconcile' | 'resume';
|
|
156
|
+
/** Names the case; all of them map to the same recovery decision. */
|
|
157
|
+
reasonDetail: string;
|
|
158
|
+
providerAttempt: number;
|
|
159
|
+
/** Lookups issued by this decision's reconcile ladder (at most 7). */
|
|
160
|
+
lookups: number;
|
|
161
|
+
lookupErrors: number;
|
|
162
|
+
deleteConflicts: number;
|
|
163
|
+
elapsedMs: number;
|
|
164
|
+
/** Known only in the process that sent the create. */
|
|
165
|
+
createResponseReceived: boolean | null;
|
|
166
|
+
createLastObservedState: string | null;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const NO_DAYTONA_NAMED_SANDBOX_LOOKUP: DaytonaNamedSandboxObservation = {
|
|
170
|
+
lookup: 'unsupported',
|
|
171
|
+
sandboxState: null,
|
|
172
|
+
observedSandboxId: null,
|
|
173
|
+
providerErrorReason: null,
|
|
174
|
+
lookupErrorClass: null,
|
|
175
|
+
lookupErrno: null,
|
|
176
|
+
lookupErrorStatus: null,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Both observers are total: they run between a provider lookup and the
|
|
180
|
+
// decision it feeds, so a failure to describe the lookup must not surface.
|
|
181
|
+
function observeDaytonaNamedSandbox(
|
|
182
|
+
sandbox: DaytonaSandbox,
|
|
183
|
+
): DaytonaNamedSandboxObservation {
|
|
184
|
+
try {
|
|
185
|
+
const record = sandbox as { id?: unknown; errorReason?: unknown };
|
|
186
|
+
return {
|
|
187
|
+
...NO_DAYTONA_NAMED_SANDBOX_LOOKUP,
|
|
188
|
+
lookup: 'found',
|
|
189
|
+
sandboxState: daytonaSandboxStateForLog(sandbox.state) ?? 'missing',
|
|
190
|
+
observedSandboxId:
|
|
191
|
+
typeof record.id === 'string' && record.id
|
|
192
|
+
? record.id.slice(0, 64)
|
|
193
|
+
: null,
|
|
194
|
+
providerErrorReason: boundedDaytonaProviderTextForLog(record.errorReason),
|
|
195
|
+
};
|
|
196
|
+
} catch {
|
|
197
|
+
return {
|
|
198
|
+
...NO_DAYTONA_NAMED_SANDBOX_LOOKUP,
|
|
199
|
+
lookup: 'found',
|
|
200
|
+
sandboxState: 'unreadable',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function observeDaytonaNamedSandboxLookupFailure(
|
|
206
|
+
error: unknown,
|
|
207
|
+
): DaytonaNamedSandboxObservation {
|
|
208
|
+
try {
|
|
209
|
+
return {
|
|
210
|
+
...NO_DAYTONA_NAMED_SANDBOX_LOOKUP,
|
|
211
|
+
lookup: isDaytonaSandboxNotFound(error) ? 'not_found' : 'error',
|
|
212
|
+
lookupErrorClass: daytonaErrorClassForLog(error),
|
|
213
|
+
lookupErrno: daytonaErrorCodeForLog(error),
|
|
214
|
+
lookupErrorStatus: daytonaErrorStatusForLog(error),
|
|
215
|
+
};
|
|
216
|
+
} catch {
|
|
217
|
+
return { ...NO_DAYTONA_NAMED_SANDBOX_LOOKUP, lookup: 'error' };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function daytonaTransitionPendingReasonDetail(
|
|
222
|
+
phase: DaytonaSandboxTransitionPendingLogDetail['phase'],
|
|
223
|
+
observation: DaytonaNamedSandboxObservation,
|
|
224
|
+
deleteConflicts: number,
|
|
225
|
+
): string {
|
|
226
|
+
if (observation.lookup !== 'found') {
|
|
227
|
+
return `${phase}:lookup_${observation.lookup}`;
|
|
228
|
+
}
|
|
229
|
+
// Reconcile reaches a pending decision with a visible sandbox only after
|
|
230
|
+
// every delete of it reported an in-progress state change.
|
|
231
|
+
return deleteConflicts > 0
|
|
232
|
+
? `${phase}:delete_conflict:${observation.sandboxState}`
|
|
233
|
+
: `${phase}:sandbox_state:${observation.sandboxState}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The log detail for one pending decision. Total by construction: failing to
|
|
238
|
+
* describe a decision must never replace the decision.
|
|
239
|
+
*/
|
|
240
|
+
function daytonaTransitionPendingLogDetail(input: {
|
|
241
|
+
phase: DaytonaSandboxTransitionPendingLogDetail['phase'];
|
|
242
|
+
observed: TimedOutDaytonaSandboxReconcileObserved;
|
|
243
|
+
providerAttempt: number;
|
|
244
|
+
launchOutcome: DaytonaLaunchOutcomeDiagnostic | null;
|
|
245
|
+
}): DaytonaSandboxTransitionPendingLogDetail | null {
|
|
246
|
+
try {
|
|
247
|
+
const { last, lookups, lookupErrors, deleteConflicts, elapsedMs } =
|
|
248
|
+
input.observed;
|
|
249
|
+
return {
|
|
250
|
+
...last,
|
|
251
|
+
phase: input.phase,
|
|
252
|
+
reasonDetail: daytonaTransitionPendingReasonDetail(
|
|
253
|
+
input.phase,
|
|
254
|
+
last,
|
|
255
|
+
deleteConflicts,
|
|
256
|
+
),
|
|
257
|
+
providerAttempt: input.providerAttempt,
|
|
258
|
+
lookups,
|
|
259
|
+
lookupErrors,
|
|
260
|
+
deleteConflicts,
|
|
261
|
+
elapsedMs,
|
|
262
|
+
createResponseReceived:
|
|
263
|
+
input.launchOutcome?.createResponseReceived ?? null,
|
|
264
|
+
createLastObservedState: input.launchOutcome?.lastObservedState ?? null,
|
|
265
|
+
};
|
|
266
|
+
} catch {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
121
271
|
export function resolveDaytonaSandboxAcquisitionUnavailableReason(
|
|
122
272
|
errors: readonly string[],
|
|
123
273
|
): DaytonaSandboxAcquisitionUnavailableReason | null {
|
|
@@ -369,11 +519,21 @@ async function createOneShotDaytonaSandbox(input: {
|
|
|
369
519
|
});
|
|
370
520
|
}
|
|
371
521
|
|
|
372
|
-
|
|
522
|
+
/** Log-only tally of one reconcile ladder; it never selects the result. */
|
|
523
|
+
type TimedOutDaytonaSandboxReconcileObserved = {
|
|
524
|
+
last: DaytonaNamedSandboxObservation;
|
|
525
|
+
lookups: number;
|
|
526
|
+
lookupErrors: number;
|
|
527
|
+
deleteConflicts: number;
|
|
528
|
+
elapsedMs: number;
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
type TimedOutDaytonaSandboxReconciliation = (
|
|
373
532
|
| { kind: 'started'; sandbox: DaytonaSandbox }
|
|
374
533
|
| { kind: 'deleted' }
|
|
375
534
|
| { kind: 'not_found' }
|
|
376
|
-
| { kind: 'transition_pending' }
|
|
535
|
+
| { kind: 'transition_pending' }
|
|
536
|
+
) & { observed: TimedOutDaytonaSandboxReconcileObserved };
|
|
377
537
|
|
|
378
538
|
async function reconcileAndDeleteTimedOutDaytonaSandbox(input: {
|
|
379
539
|
daytona: DaytonaClient;
|
|
@@ -386,7 +546,19 @@ async function reconcileAndDeleteTimedOutDaytonaSandbox(input: {
|
|
|
386
546
|
runId?: string;
|
|
387
547
|
};
|
|
388
548
|
}): Promise<TimedOutDaytonaSandboxReconciliation> {
|
|
389
|
-
|
|
549
|
+
const reconcileStartedAt = Date.now();
|
|
550
|
+
let last = NO_DAYTONA_NAMED_SANDBOX_LOOKUP;
|
|
551
|
+
let lookups = 0;
|
|
552
|
+
let lookupErrors = 0;
|
|
553
|
+
let deleteConflicts = 0;
|
|
554
|
+
const observed = (): TimedOutDaytonaSandboxReconcileObserved => ({
|
|
555
|
+
last,
|
|
556
|
+
lookups,
|
|
557
|
+
lookupErrors,
|
|
558
|
+
deleteConflicts,
|
|
559
|
+
elapsedMs: Date.now() - reconcileStartedAt,
|
|
560
|
+
});
|
|
561
|
+
if (!input.daytona.get) return { kind: 'not_found', observed: observed() };
|
|
390
562
|
let lastDeleteConflict: unknown = null;
|
|
391
563
|
for (const delayMs of DAYTONA_TIMED_OUT_CREATE_RECONCILE_DELAYS_MS) {
|
|
392
564
|
input.cancellationSignal?.throwIfAborted();
|
|
@@ -394,9 +566,11 @@ async function reconcileAndDeleteTimedOutDaytonaSandbox(input: {
|
|
|
394
566
|
await waitForAcquisitionRetry(delayMs, input.cancellationSignal);
|
|
395
567
|
}
|
|
396
568
|
let sandbox: DaytonaSandbox;
|
|
569
|
+
lookups += 1;
|
|
397
570
|
try {
|
|
398
571
|
sandbox = await input.daytona.get(input.sandboxName);
|
|
399
572
|
} catch (error) {
|
|
573
|
+
last = observeDaytonaNamedSandboxLookupFailure(error);
|
|
400
574
|
const providerStatus =
|
|
401
575
|
error && typeof error === 'object' && 'statusCode' in error
|
|
402
576
|
? error.statusCode
|
|
@@ -410,7 +584,10 @@ async function reconcileAndDeleteTimedOutDaytonaSandbox(input: {
|
|
|
410
584
|
// A current absence is not proof that the sent create cannot commit.
|
|
411
585
|
// Return it to the durable operation owner without another create or
|
|
412
586
|
// releasing capacity; later recovery re-reads the same identity.
|
|
413
|
-
if (isDaytonaSandboxNotFound(error))
|
|
587
|
+
if (isDaytonaSandboxNotFound(error)) {
|
|
588
|
+
return { kind: 'not_found', observed: observed() };
|
|
589
|
+
}
|
|
590
|
+
lookupErrors += 1;
|
|
414
591
|
continue;
|
|
415
592
|
}
|
|
416
593
|
input.emitStage?.('create:reconcile_state', {
|
|
@@ -423,8 +600,9 @@ async function reconcileAndDeleteTimedOutDaytonaSandbox(input: {
|
|
|
423
600
|
// is safer and faster than attempting a conflicting delete or creating a
|
|
424
601
|
// second sandbox.
|
|
425
602
|
if (sandbox.state === 'started') {
|
|
426
|
-
return { kind: 'started', sandbox };
|
|
603
|
+
return { kind: 'started', sandbox, observed: observed() };
|
|
427
604
|
}
|
|
605
|
+
last = observeDaytonaNamedSandbox(sandbox);
|
|
428
606
|
if (input.operationIdentity) {
|
|
429
607
|
const expected = input.operationIdentity;
|
|
430
608
|
const configuredOrganizationId =
|
|
@@ -455,6 +633,7 @@ async function reconcileAndDeleteTimedOutDaytonaSandbox(input: {
|
|
|
455
633
|
// sandbox while the first one is still becoming ready.
|
|
456
634
|
if (isDaytonaSandboxStateChangeConflict(error)) {
|
|
457
635
|
lastDeleteConflict = error;
|
|
636
|
+
deleteConflicts += 1;
|
|
458
637
|
continue;
|
|
459
638
|
}
|
|
460
639
|
throw new Error(
|
|
@@ -462,12 +641,12 @@ async function reconcileAndDeleteTimedOutDaytonaSandbox(input: {
|
|
|
462
641
|
{ cause: error },
|
|
463
642
|
);
|
|
464
643
|
}
|
|
465
|
-
return { kind: 'deleted' };
|
|
644
|
+
return { kind: 'deleted', observed: observed() };
|
|
466
645
|
}
|
|
467
646
|
if (lastDeleteConflict) {
|
|
468
|
-
return { kind: 'transition_pending' };
|
|
647
|
+
return { kind: 'transition_pending', observed: observed() };
|
|
469
648
|
}
|
|
470
|
-
return { kind: 'not_found' };
|
|
649
|
+
return { kind: 'not_found', observed: observed() };
|
|
471
650
|
}
|
|
472
651
|
|
|
473
652
|
function daytonaSandboxName(providerOperationToken?: string | null): string {
|
|
@@ -553,8 +732,17 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
553
732
|
);
|
|
554
733
|
}
|
|
555
734
|
// Neither absence nor an ongoing transition proves that create cannot
|
|
556
|
-
// commit. Retain the durable identity and its capacity reservation.
|
|
557
|
-
|
|
735
|
+
// commit. Retain the durable identity and its capacity reservation. The
|
|
736
|
+
// detail only records which of the two Daytona reported, for the log.
|
|
737
|
+
throw new DaytonaSandboxTransitionPendingError(
|
|
738
|
+
sandboxName,
|
|
739
|
+
daytonaTransitionPendingLogDetail({
|
|
740
|
+
phase: 'resume',
|
|
741
|
+
observed: reconciliation.observed,
|
|
742
|
+
providerAttempt: attempt,
|
|
743
|
+
launchOutcome: null,
|
|
744
|
+
}),
|
|
745
|
+
);
|
|
558
746
|
}
|
|
559
747
|
let sandbox: DaytonaSandbox;
|
|
560
748
|
try {
|
|
@@ -579,17 +767,29 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
579
767
|
errorClass: daytonaCreateErrorClass(error),
|
|
580
768
|
});
|
|
581
769
|
errors.push(message);
|
|
770
|
+
// Log-only: whether the create request returned before the deadline and
|
|
771
|
+
// what Daytona last reported, so an unknown outcome is attributable.
|
|
772
|
+
const launchOutcome =
|
|
773
|
+
error instanceof DaytonaLaunchOutcomeUnknownError
|
|
774
|
+
? error.diagnostic
|
|
775
|
+
: null;
|
|
776
|
+
const createFailureLogDetail = {
|
|
777
|
+
createErrorClass: daytonaCreateErrorClass(error),
|
|
778
|
+
...(launchOutcome ?? {}),
|
|
779
|
+
};
|
|
582
780
|
input.emitStage('create:attempt_failed', {
|
|
583
781
|
attempt,
|
|
584
782
|
elapsedMs: Date.now() - input.startedAt,
|
|
585
783
|
attemptElapsedMs: Date.now() - attemptStartedAt,
|
|
586
784
|
error: message,
|
|
785
|
+
...createFailureLogDetail,
|
|
587
786
|
});
|
|
588
787
|
console.warn('[play-runner.daytona.create_attempt_failed]', {
|
|
589
788
|
workflowId: input.context.workflowId ?? null,
|
|
590
789
|
runId: input.context.runId ?? null,
|
|
591
790
|
attempt,
|
|
592
791
|
error: message,
|
|
792
|
+
...createFailureLogDetail,
|
|
593
793
|
});
|
|
594
794
|
const immediateFallbackReason =
|
|
595
795
|
resolveDaytonaSandboxAcquisitionUnavailableReason([message]);
|
|
@@ -640,9 +840,21 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
640
840
|
};
|
|
641
841
|
}
|
|
642
842
|
await recordCreateFailure();
|
|
843
|
+
// Same decision as before; the detail records what the ladder saw.
|
|
844
|
+
const reconcileObserved = reconciliation.observed;
|
|
845
|
+
const reconcilePending = (): DaytonaSandboxTransitionPendingError =>
|
|
846
|
+
new DaytonaSandboxTransitionPendingError(
|
|
847
|
+
sandboxName,
|
|
848
|
+
daytonaTransitionPendingLogDetail({
|
|
849
|
+
phase: 'create_reconcile',
|
|
850
|
+
observed: reconcileObserved,
|
|
851
|
+
providerAttempt: attempt,
|
|
852
|
+
launchOutcome,
|
|
853
|
+
}),
|
|
854
|
+
);
|
|
643
855
|
if (reconciliation.kind === 'transition_pending') {
|
|
644
856
|
if (reservation?.providerOperationToken) {
|
|
645
|
-
throw
|
|
857
|
+
throw reconcilePending();
|
|
646
858
|
}
|
|
647
859
|
throw new Error(
|
|
648
860
|
`Daytona timed-out sandbox ${sandboxName} remained in a state transition across the bounded cleanup window. Modal fallback suppressed.`,
|
|
@@ -663,7 +875,7 @@ async function createRetriedOneShotDaytonaSandbox(input: {
|
|
|
663
875
|
);
|
|
664
876
|
}
|
|
665
877
|
if (reservation?.providerOperationToken) {
|
|
666
|
-
throw
|
|
878
|
+
throw reconcilePending();
|
|
667
879
|
}
|
|
668
880
|
throw new Error(
|
|
669
881
|
`${message} Daytona did not return a cleanup-addressable sandbox identity; Modal fallback suppressed. The named ephemeral sandbox retains Daytona's auto-delete-on-stop backstop.`,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createDaytonaLaunchClient,
|
|
3
|
+
redactDaytonaErrorDetail,
|
|
4
|
+
} from './daytona-launch-transport';
|
|
2
5
|
import type { DaytonaClientOptions } from '../../daytona-runtime-config';
|
|
3
6
|
import {
|
|
4
7
|
createDaytonaControlClient,
|
|
@@ -328,23 +331,6 @@ function numberProperty(value: unknown, key: string): number | null {
|
|
|
328
331
|
: null;
|
|
329
332
|
}
|
|
330
333
|
|
|
331
|
-
function redactDaytonaErrorDetail(value: string): string {
|
|
332
|
-
return value
|
|
333
|
-
.replace(
|
|
334
|
-
/authorization\s*[=:]\s*bearer\s+[^\s,;]+/gi,
|
|
335
|
-
'Authorization=Bearer [redacted]',
|
|
336
|
-
)
|
|
337
|
-
.replace(
|
|
338
|
-
/(authorization\s*[=:]\s*)(?!bearer\s+\[redacted\])[^\s,;]+/gi,
|
|
339
|
-
'$1[redacted]',
|
|
340
|
-
)
|
|
341
|
-
.replace(/(bearer\s+)[^\s,;]+/gi, '$1[redacted]')
|
|
342
|
-
.replace(/((?:api[_-]?key|token)\s*[=:]\s*)[^\s,;]+/gi, '$1[redacted]')
|
|
343
|
-
.replace(/\s+/g, ' ')
|
|
344
|
-
.trim()
|
|
345
|
-
.slice(0, 1_000);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
334
|
/**
|
|
349
335
|
* Preserve the actionable shape of a Daytona control-plane failure without
|
|
350
336
|
* copying credentials or an arbitrarily large upstream response into logs.
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts
CHANGED
|
@@ -48,6 +48,7 @@ import { RUNTIME_RELIABILITY_POLICY } from '../runtime-reliability-policy';
|
|
|
48
48
|
import type { RuntimeTrafficPolicy } from '../runtime-traffic-policy';
|
|
49
49
|
import { runtimeLimitExceededCause } from '../run-failure';
|
|
50
50
|
import {
|
|
51
|
+
playRunRecoveryLogDetail,
|
|
51
52
|
recoveryForDaytonaSandboxAcquisitionUnavailable,
|
|
52
53
|
recoveryForRuntimeInitializationTransportFailure,
|
|
53
54
|
recoveryForRuntimeSchedulerAdmissionFailure,
|
|
@@ -4359,6 +4360,9 @@ async function executeAssignedAbsurdAttempt(input: {
|
|
|
4359
4360
|
}),
|
|
4360
4361
|
);
|
|
4361
4362
|
if (recovery.kind === 'deferred') {
|
|
4363
|
+
// Log-only. The persisted reason groups several provider conditions
|
|
4364
|
+
// into one decision; the detail names the one that was observed.
|
|
4365
|
+
const detail = playRunRecoveryLogDetail(recoveryError);
|
|
4362
4366
|
console.warn('[absurd-runtime.worker] pre_code_recovery_deferred', {
|
|
4363
4367
|
runId,
|
|
4364
4368
|
generation,
|
|
@@ -4367,6 +4371,7 @@ async function executeAssignedAbsurdAttempt(input: {
|
|
|
4367
4371
|
deferCount: recovery.deferCount,
|
|
4368
4372
|
deadlineAtMs: recovery.deadlineAtMs,
|
|
4369
4373
|
delaySeconds: recovery.delaySeconds,
|
|
4374
|
+
...(detail ? { detail } : {}),
|
|
4370
4375
|
});
|
|
4371
4376
|
throw new DeferTask(
|
|
4372
4377
|
`Platform recovery deferred for ${recovery.dependency}: ${recovery.reason}`,
|
|
@@ -15682,6 +15682,34 @@ export async function getPostgresSchedulerObservations(
|
|
|
15682
15682
|
return result.rows.map(mapObservationRow);
|
|
15683
15683
|
}
|
|
15684
15684
|
|
|
15685
|
+
/** Read the newest settled observation for one node across a bounded page of runs. */
|
|
15686
|
+
export async function getPostgresSchedulerObservationsForRuns(
|
|
15687
|
+
client: PostgresSchedulerQueryClient,
|
|
15688
|
+
input: { runIds: readonly string[]; nodeId: string },
|
|
15689
|
+
options: PostgresSchedulerReceiptOptions,
|
|
15690
|
+
): Promise<DocflowObservationRecord[]> {
|
|
15691
|
+
const runIds = [
|
|
15692
|
+
...new Set(input.runIds.map((runId) => runId.trim()).filter(Boolean)),
|
|
15693
|
+
];
|
|
15694
|
+
if (runIds.length === 0) return [];
|
|
15695
|
+
const result = await client.query<ObservationRow>(
|
|
15696
|
+
`
|
|
15697
|
+
SELECT DISTINCT ON (run_id)
|
|
15698
|
+
run_id, attempt, node_id, invocation_seq, invocation_id, status,
|
|
15699
|
+
inputs_json, outputs_json, inputs_truncated, outputs_truncated,
|
|
15700
|
+
error, started_at, settled_at
|
|
15701
|
+
FROM ${tableName('observations', options)}
|
|
15702
|
+
WHERE org_id = $1
|
|
15703
|
+
AND run_id = ANY($2::text[])
|
|
15704
|
+
AND node_id = $3
|
|
15705
|
+
AND status IN ('completed', 'failed')
|
|
15706
|
+
ORDER BY run_id, attempt DESC, invocation_seq DESC
|
|
15707
|
+
`,
|
|
15708
|
+
[receiptOrgId(options), runIds, input.nodeId],
|
|
15709
|
+
);
|
|
15710
|
+
return result.rows.map(mapObservationRow);
|
|
15711
|
+
}
|
|
15712
|
+
|
|
15685
15713
|
export async function countPostgresSchedulerReceipts(
|
|
15686
15714
|
client: PostgresSchedulerQueryClient,
|
|
15687
15715
|
input: {
|
|
@@ -18151,6 +18179,20 @@ async function claimPostgresSchedulerOutboxPrefix(
|
|
|
18151
18179
|
: supportsTerminalBatchMarker && input.allowIngressPrefix === true
|
|
18152
18180
|
? `(head.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents})`
|
|
18153
18181
|
: 'false';
|
|
18182
|
+
const findSearchBlockersOnce = seekRunHead && input.searchOnly === true;
|
|
18183
|
+
const blockingPredecessorSql = (alias: string) => `(
|
|
18184
|
+
(${alias}.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents}
|
|
18185
|
+
AND NOT (${ingressPrefixSql}))
|
|
18186
|
+
OR (${supportsTerminalBatchMarker && input.allowIngressPrefix === true ? `head.delivery_batch_started_at IS NOT NULL AND ${alias}.delivery_batch_started_at IS NULL` : 'false'})
|
|
18187
|
+
OR ${alias}.failed_at IS NOT NULL
|
|
18188
|
+
OR coalesce(${alias}.next_attempt_at, ${alias}.created_at) > now()
|
|
18189
|
+
OR (
|
|
18190
|
+
$3::text IS NOT NULL
|
|
18191
|
+
AND ${alias}.lease_expires_at IS NOT NULL
|
|
18192
|
+
AND ${alias}.lease_expires_at > now()
|
|
18193
|
+
AND ${alias}.lease_owner IS DISTINCT FROM $3::text
|
|
18194
|
+
)
|
|
18195
|
+
)`;
|
|
18154
18196
|
return await client.query<{
|
|
18155
18197
|
row_kind: 'claimed' | 'delivered';
|
|
18156
18198
|
event_id: string;
|
|
@@ -18193,6 +18235,7 @@ async function claimPostgresSchedulerOutboxPrefix(
|
|
|
18193
18235
|
: ''
|
|
18194
18236
|
} head AS MATERIALIZED (
|
|
18195
18237
|
SELECT candidate.event_id, candidate.run_id, candidate.event_type,
|
|
18238
|
+
${findSearchBlockersOnce ? 'candidate.created_at,' : ''}
|
|
18196
18239
|
${supportsTerminalBatchMarker ? 'candidate.delivery_batch_started_at' : 'NULL::timestamptz'} AS delivery_batch_started_at
|
|
18197
18240
|
FROM ${tableName('outbox', options)} AS candidate
|
|
18198
18241
|
${seekRunHead ? 'JOIN pending_head ON pending_head.event_id = candidate.event_id' : ''}
|
|
@@ -18274,7 +18317,41 @@ async function claimPostgresSchedulerOutboxPrefix(
|
|
|
18274
18317
|
}
|
|
18275
18318
|
FOR UPDATE SKIP LOCKED
|
|
18276
18319
|
LIMIT 1
|
|
18277
|
-
),
|
|
18320
|
+
), ${
|
|
18321
|
+
findSearchBlockersOnce
|
|
18322
|
+
? `first_blocker AS MATERIALIZED (
|
|
18323
|
+
SELECT predecessor.event_type, predecessor.created_at, predecessor.event_id
|
|
18324
|
+
FROM ${tableName('outbox', options)} AS predecessor
|
|
18325
|
+
JOIN head ON head.run_id = predecessor.run_id
|
|
18326
|
+
WHERE predecessor.run_id = $2::text
|
|
18327
|
+
AND ${pending('predecessor')}
|
|
18328
|
+
AND ${searchLaneSql('predecessor')} = true
|
|
18329
|
+
-- The oldest pending head passed eligibility above. Only later
|
|
18330
|
+
-- facts can introduce an ordinary blocker for this prefix.
|
|
18331
|
+
AND ${runLedgerPositionSql('predecessor')} > (
|
|
18332
|
+
(SELECT CASE head.event_type WHEN ${RUN_EVENT_KIND_SQL_LITERAL.runCreated} THEN 0 WHEN ${RUN_EVENT_KIND_SQL_LITERAL.runStarted} THEN 1 ELSE 2 END FROM head),
|
|
18333
|
+
(SELECT head.created_at FROM head),
|
|
18334
|
+
(SELECT head.event_id FROM head)
|
|
18335
|
+
)
|
|
18336
|
+
AND ${blockingPredecessorSql('predecessor')}
|
|
18337
|
+
ORDER BY ${runLedgerOrderSql('predecessor')}
|
|
18338
|
+
LIMIT 1
|
|
18339
|
+
), first_started_terminal AS MATERIALIZED (
|
|
18340
|
+
SELECT terminal_predecessor.event_type, terminal_predecessor.created_at, terminal_predecessor.event_id
|
|
18341
|
+
FROM ${tableName('outbox', options)} AS terminal_predecessor
|
|
18342
|
+
JOIN head ON head.run_id = terminal_predecessor.run_id
|
|
18343
|
+
WHERE terminal_predecessor.run_id = $2::text
|
|
18344
|
+
AND ${pending('terminal_predecessor')}
|
|
18345
|
+
AND ${searchLaneSql('terminal_predecessor')} = true
|
|
18346
|
+
AND ${supportsTerminalBatchMarker ? 'terminal_predecessor.delivery_batch_started_at IS NOT NULL' : 'false'}
|
|
18347
|
+
AND terminal_predecessor.event_type IN (
|
|
18348
|
+
${RUN_EVENT_KIND_SQL_LITERAL.runCompleted}, ${RUN_EVENT_KIND_SQL_LITERAL.runFailed}, ${RUN_EVENT_KIND_SQL_LITERAL.runCancelled}
|
|
18349
|
+
)
|
|
18350
|
+
ORDER BY ${runLedgerOrderSql('terminal_predecessor')}
|
|
18351
|
+
LIMIT 1
|
|
18352
|
+
),`
|
|
18353
|
+
: ''
|
|
18354
|
+
}candidates AS MATERIALIZED (
|
|
18278
18355
|
SELECT outbox.event_id
|
|
18279
18356
|
FROM ${tableName('outbox', options)} AS outbox
|
|
18280
18357
|
JOIN head ON head.run_id = outbox.run_id
|
|
@@ -18313,7 +18390,20 @@ async function claimPostgresSchedulerOutboxPrefix(
|
|
|
18313
18390
|
-- that the replay included a new event. A terminal which has not
|
|
18314
18391
|
-- started a batch yet remains an ordinary causal event and can be
|
|
18315
18392
|
-- claimed with its existing contiguous prefix.
|
|
18393
|
+
${
|
|
18394
|
+
findSearchBlockersOnce
|
|
18395
|
+
? `-- Each cutoff reads this run's blockers once, even when stale
|
|
18396
|
+
-- statistics make a broad pending scan look cheaper than a seek.
|
|
18316
18397
|
AND NOT EXISTS (
|
|
18398
|
+
SELECT 1 FROM first_started_terminal AS blocker
|
|
18399
|
+
WHERE ${supportsTerminalBatchMarker ? 'outbox.delivery_batch_started_at IS NULL' : 'true'}
|
|
18400
|
+
AND ${runLedgerPositionSql('blocker')} < ${runLedgerPositionSql('outbox')}
|
|
18401
|
+
)
|
|
18402
|
+
AND NOT EXISTS (
|
|
18403
|
+
SELECT 1 FROM first_blocker AS blocker
|
|
18404
|
+
WHERE ${runLedgerPositionSql('blocker')} < ${runLedgerPositionSql('outbox')}
|
|
18405
|
+
)`
|
|
18406
|
+
: ` AND NOT EXISTS (
|
|
18317
18407
|
SELECT 1
|
|
18318
18408
|
FROM ${tableName('outbox', options)} AS terminal_predecessor
|
|
18319
18409
|
WHERE terminal_predecessor.run_id = outbox.run_id
|
|
@@ -18341,20 +18431,10 @@ async function claimPostgresSchedulerOutboxPrefix(
|
|
|
18341
18431
|
WHERE predecessor.run_id = outbox.run_id
|
|
18342
18432
|
AND ${pending('predecessor')}
|
|
18343
18433
|
AND ${runLedgerPrecedesSql('predecessor', 'outbox', false, input.searchOnly === true)}
|
|
18344
|
-
AND (
|
|
18345
|
-
(predecessor.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents}
|
|
18346
|
-
AND NOT (${ingressPrefixSql}))
|
|
18347
|
-
OR (${supportsTerminalBatchMarker && input.allowIngressPrefix === true ? 'head.delivery_batch_started_at IS NOT NULL AND predecessor.delivery_batch_started_at IS NULL' : 'false'})
|
|
18348
|
-
OR predecessor.failed_at IS NOT NULL
|
|
18349
|
-
OR coalesce(predecessor.next_attempt_at, predecessor.created_at) > now()
|
|
18350
|
-
OR (
|
|
18351
|
-
$3::text IS NOT NULL
|
|
18352
|
-
AND predecessor.lease_expires_at IS NOT NULL
|
|
18353
|
-
AND predecessor.lease_expires_at > now()
|
|
18354
|
-
AND predecessor.lease_owner IS DISTINCT FROM $3::text
|
|
18355
|
-
)
|
|
18356
|
-
)
|
|
18434
|
+
AND ${blockingPredecessorSql('predecessor')}
|
|
18357
18435
|
)
|
|
18436
|
+
`
|
|
18437
|
+
}
|
|
18358
18438
|
ORDER BY ${runLedgerOrderSql('outbox')}
|
|
18359
18439
|
FOR UPDATE OF outbox
|
|
18360
18440
|
LIMIT $1
|