deepline 0.3.43 → 0.3.45
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/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/observability/scheduled-work.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1540 -265
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +11 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +22 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +16 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +418 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/index.ts +452 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +304 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/testkit.ts +83 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/always-fresh-adapter.ts +50 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/contract.ts +135 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +291 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +335 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +904 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +522 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/testkit.ts +165 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/install-integrity.json +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SandboxExecutionDependencies,
|
|
3
|
+
SandboxExecutionInput,
|
|
4
|
+
SandboxExecutionJob,
|
|
5
|
+
SandboxExecutionLifecycle,
|
|
6
|
+
SandboxExecutionOutcome,
|
|
7
|
+
SandboxExecutionPortOutcome,
|
|
8
|
+
SandboxExecutionResource,
|
|
9
|
+
SandboxRunDependencies,
|
|
10
|
+
SandboxRunInput,
|
|
11
|
+
SandboxRunsJob,
|
|
12
|
+
} from './contract';
|
|
13
|
+
import { SandboxResourceFenceLostError } from './contract';
|
|
14
|
+
|
|
15
|
+
export type {
|
|
16
|
+
SandboxAcquisition,
|
|
17
|
+
SandboxCreateIntent,
|
|
18
|
+
SandboxExecutionAdmission,
|
|
19
|
+
SandboxExecutionCreateIntents,
|
|
20
|
+
SandboxExecutionDependencies,
|
|
21
|
+
SandboxExecutionInput,
|
|
22
|
+
SandboxExecutionJob,
|
|
23
|
+
SandboxExecutionLifecycle,
|
|
24
|
+
SandboxExecutionLifecycleEvents,
|
|
25
|
+
SandboxExecutionMode,
|
|
26
|
+
SandboxExecutionOutcome,
|
|
27
|
+
SandboxExecutionPort,
|
|
28
|
+
SandboxExecutionPortOutcome,
|
|
29
|
+
SandboxExecutionResource,
|
|
30
|
+
SandboxExecutionResourceFacts,
|
|
31
|
+
SandboxProviderLifecycleEvent,
|
|
32
|
+
SandboxPreCodeDeferralReason,
|
|
33
|
+
SandboxProvider,
|
|
34
|
+
SandboxResourceFacts,
|
|
35
|
+
SandboxRunDependencies,
|
|
36
|
+
SandboxRunInput,
|
|
37
|
+
SandboxRunOutcome,
|
|
38
|
+
SandboxRunResource,
|
|
39
|
+
SandboxRunnerObservation,
|
|
40
|
+
SandboxRunnerObserver,
|
|
41
|
+
SandboxRunnerStart,
|
|
42
|
+
SandboxRunsJob,
|
|
43
|
+
} from './contract';
|
|
44
|
+
|
|
45
|
+
export { SandboxResourceFenceLostError } from './contract';
|
|
46
|
+
export {
|
|
47
|
+
createPlayRunnerBackendSandboxExecutionAdapter,
|
|
48
|
+
playRunnerRuntimeResourceFromSandboxExecutionResource,
|
|
49
|
+
type SandboxRunnerBackendAdapterDependencies,
|
|
50
|
+
type SandboxRunnerProgress,
|
|
51
|
+
type SandboxRunnerReadiness,
|
|
52
|
+
type SandboxRunnerResultInterpreter,
|
|
53
|
+
} from './runner-backend-adapter';
|
|
54
|
+
export { SandboxRunnerPreCodeDeferralError } from '../runner-backends/types';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates the runner-portable Sandbox Runs job.
|
|
58
|
+
*
|
|
59
|
+
* The only sequencing decision hidden here is resource-fact-before-command:
|
|
60
|
+
* provider adapters may acquire, and scheduler adapters may fence/record, but
|
|
61
|
+
* neither can cause the runner command to start out of order. Cleanup and
|
|
62
|
+
* attempt retry policy remain their own jobs because they have different
|
|
63
|
+
* durable authority.
|
|
64
|
+
*/
|
|
65
|
+
export function createSandboxRuns<Handle>(
|
|
66
|
+
dependencies: SandboxRunDependencies<Handle>,
|
|
67
|
+
): SandboxRunsJob {
|
|
68
|
+
return {
|
|
69
|
+
async execute(input: SandboxRunInput) {
|
|
70
|
+
validateInput(input);
|
|
71
|
+
const acquisition = await dependencies.acquire.acquire(input);
|
|
72
|
+
if (acquisition.kind === 'pre_code_deferred') {
|
|
73
|
+
return acquisition;
|
|
74
|
+
}
|
|
75
|
+
if (acquisition.kind === 'acquisition_unknown') {
|
|
76
|
+
return acquisition;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
validateResource(acquisition.resource);
|
|
80
|
+
const recorded = await dependencies.resourceFacts.record({
|
|
81
|
+
attempt: input.attempt,
|
|
82
|
+
authority: input.authority,
|
|
83
|
+
resource: acquisition.resource,
|
|
84
|
+
});
|
|
85
|
+
if (recorded.kind === 'fence_lost') {
|
|
86
|
+
return {
|
|
87
|
+
kind: 'resource_recording_rejected',
|
|
88
|
+
resource: acquisition.resource,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const started = await dependencies.runner.start({
|
|
93
|
+
attempt: input.attempt,
|
|
94
|
+
resource: acquisition.resource,
|
|
95
|
+
handle: acquisition.handle,
|
|
96
|
+
});
|
|
97
|
+
if (started.kind === 'not_started') {
|
|
98
|
+
return {
|
|
99
|
+
kind: 'post_record_start_failed',
|
|
100
|
+
resource: acquisition.resource,
|
|
101
|
+
reason: started.reason,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (started.kind === 'start_unknown') {
|
|
105
|
+
return {
|
|
106
|
+
kind: 'runner_start_unknown',
|
|
107
|
+
resource: acquisition.resource,
|
|
108
|
+
reason: started.reason,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const observed = await dependencies.observer.observe({
|
|
113
|
+
attempt: input.attempt,
|
|
114
|
+
resource: acquisition.resource,
|
|
115
|
+
observation: started.observation,
|
|
116
|
+
});
|
|
117
|
+
if (observed.kind === 'heartbeat_observed') {
|
|
118
|
+
return {
|
|
119
|
+
kind: 'parked',
|
|
120
|
+
resource: acquisition.resource,
|
|
121
|
+
observation: started.observation,
|
|
122
|
+
heartbeatAt: observed.heartbeatAt,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (observed.kind === 'terminal_observed') {
|
|
126
|
+
return {
|
|
127
|
+
kind: 'terminal_observed',
|
|
128
|
+
resource: acquisition.resource,
|
|
129
|
+
terminal: observed.terminal,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
kind: 'runner_start_unconfirmed',
|
|
134
|
+
resource: acquisition.resource,
|
|
135
|
+
observation: started.observation,
|
|
136
|
+
reason: observed.reason,
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Creates the production-shaped Sandbox Runs job.
|
|
144
|
+
*
|
|
145
|
+
* The execution Adapter is deliberately one-shot: it wraps one invocation of
|
|
146
|
+
* a provider runner backend and reports durable lifecycle facts through the
|
|
147
|
+
* supplied capability. Splitting that Adapter into synthetic `acquire` and
|
|
148
|
+
* `start` calls would move the resource-fact fence after a provider boundary
|
|
149
|
+
* for the existing Daytona and Modal implementations.
|
|
150
|
+
*/
|
|
151
|
+
export function createSandboxExecutionJob<
|
|
152
|
+
Launch,
|
|
153
|
+
Checkpoint,
|
|
154
|
+
Suspension,
|
|
155
|
+
RunnerResult,
|
|
156
|
+
>(
|
|
157
|
+
dependencies: SandboxExecutionDependencies<
|
|
158
|
+
Launch,
|
|
159
|
+
Checkpoint,
|
|
160
|
+
Suspension,
|
|
161
|
+
RunnerResult
|
|
162
|
+
>,
|
|
163
|
+
): SandboxExecutionJob<Launch, Checkpoint, Suspension, RunnerResult> {
|
|
164
|
+
return {
|
|
165
|
+
async execute(input) {
|
|
166
|
+
validateInput(input);
|
|
167
|
+
const state = createExecutionLifecycle(input, dependencies);
|
|
168
|
+
try {
|
|
169
|
+
const outcome = await dependencies.execution.execute({
|
|
170
|
+
execution: input,
|
|
171
|
+
lifecycle: state.lifecycle,
|
|
172
|
+
});
|
|
173
|
+
return normalizeExecutionOutcome(outcome, state, input.mode.kind);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (error instanceof SandboxResourceFenceLostError) {
|
|
176
|
+
return {
|
|
177
|
+
kind: 'resource_recording_rejected',
|
|
178
|
+
resources: state.resources(),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function createExecutionLifecycle<Launch, Checkpoint, Suspension, RunnerResult>(
|
|
188
|
+
input: SandboxExecutionInput<Launch, Checkpoint, Suspension>,
|
|
189
|
+
dependencies: SandboxExecutionDependencies<
|
|
190
|
+
Launch,
|
|
191
|
+
Checkpoint,
|
|
192
|
+
Suspension,
|
|
193
|
+
RunnerResult
|
|
194
|
+
>,
|
|
195
|
+
): {
|
|
196
|
+
lifecycle: SandboxExecutionLifecycle;
|
|
197
|
+
resources(): readonly SandboxExecutionResource[];
|
|
198
|
+
commandAccepted(): boolean;
|
|
199
|
+
runnerReady(): boolean;
|
|
200
|
+
} {
|
|
201
|
+
const resources = new Map<string, SandboxExecutionResource>();
|
|
202
|
+
const acceptedResources = new Set<string>();
|
|
203
|
+
const commandAcceptedResources = new Set<string>();
|
|
204
|
+
let commandAccepted = false;
|
|
205
|
+
let runnerReady = false;
|
|
206
|
+
|
|
207
|
+
const requireAdmission = () => {
|
|
208
|
+
if (!dependencies.admission) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
'Sandbox Runs execution Adapter requested scheduler admission, but no admission Adapter is configured.',
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return dependencies.admission;
|
|
214
|
+
};
|
|
215
|
+
const requireCreateIntents = () => {
|
|
216
|
+
if (!dependencies.createIntents) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
'Sandbox Runs execution Adapter crossed the Modal create boundary, but no create-intent Adapter is configured.',
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return dependencies.createIntents;
|
|
222
|
+
};
|
|
223
|
+
const requireLifecycleEvents = () => {
|
|
224
|
+
if (!dependencies.lifecycleEvents) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
'Sandbox Runs execution Adapter emitted a provider lifecycle event, but no lifecycle-event Adapter is configured.',
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
return dependencies.lifecycleEvents;
|
|
230
|
+
};
|
|
231
|
+
const recordResult = (
|
|
232
|
+
result: { kind: 'recorded' | 'resolved' } | { kind: 'fence_lost' },
|
|
233
|
+
) => {
|
|
234
|
+
if (result.kind === 'fence_lost') throw new SandboxResourceFenceLostError();
|
|
235
|
+
};
|
|
236
|
+
const rememberResource = (resource: SandboxExecutionResource) => {
|
|
237
|
+
validateResource(resource);
|
|
238
|
+
resources.set(resourceIdentity(resource), resource);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
lifecycle: {
|
|
243
|
+
beforeFirstProviderCreate: async () =>
|
|
244
|
+
await requireAdmission().beforeFirstProviderCreate({
|
|
245
|
+
attempt: input.attempt,
|
|
246
|
+
authority: input.authority,
|
|
247
|
+
}),
|
|
248
|
+
reserveCapacity: async (value) =>
|
|
249
|
+
await requireAdmission().reserveCapacity(value),
|
|
250
|
+
releaseCapacity: async (value) =>
|
|
251
|
+
await requireAdmission().releaseCapacity(value),
|
|
252
|
+
claimProviderCircuit: async (value) =>
|
|
253
|
+
await requireAdmission().claimProviderCircuit(value),
|
|
254
|
+
markProviderHealthy: async (value) =>
|
|
255
|
+
await requireAdmission().markProviderHealthy(value),
|
|
256
|
+
recordCreateIntent: async (intent) => {
|
|
257
|
+
validateCreateIntent(intent);
|
|
258
|
+
recordResult(
|
|
259
|
+
await requireCreateIntents().record({
|
|
260
|
+
attempt: input.attempt,
|
|
261
|
+
authority: input.authority,
|
|
262
|
+
intent,
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
},
|
|
266
|
+
resolveCreateIntent: async () => {
|
|
267
|
+
recordResult(
|
|
268
|
+
await requireCreateIntents().resolve({
|
|
269
|
+
attempt: input.attempt,
|
|
270
|
+
authority: input.authority,
|
|
271
|
+
}),
|
|
272
|
+
);
|
|
273
|
+
},
|
|
274
|
+
recordProviderLifecycleEvent: async (event) => {
|
|
275
|
+
validateLifecycleEvent(event);
|
|
276
|
+
recordResult(
|
|
277
|
+
await requireLifecycleEvents().record({
|
|
278
|
+
attempt: input.attempt,
|
|
279
|
+
authority: input.authority,
|
|
280
|
+
event,
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
283
|
+
},
|
|
284
|
+
recordResource: async (resource) => {
|
|
285
|
+
rememberResource(resource);
|
|
286
|
+
recordResult(
|
|
287
|
+
await dependencies.resourceFacts.record({
|
|
288
|
+
attempt: input.attempt,
|
|
289
|
+
authority: input.authority,
|
|
290
|
+
resource,
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
acceptedResources.add(resourceIdentity(resource));
|
|
294
|
+
},
|
|
295
|
+
recordLateResource: async (resource) => {
|
|
296
|
+
rememberResource(resource);
|
|
297
|
+
recordResult(
|
|
298
|
+
await dependencies.resourceFacts.recordLate({
|
|
299
|
+
attempt: input.attempt,
|
|
300
|
+
authority: input.authority,
|
|
301
|
+
resource,
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
},
|
|
305
|
+
commandAccepted: ({ provider, resource }) => {
|
|
306
|
+
if (provider === 'local') {
|
|
307
|
+
if (resource) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
'Sandbox Runs local command acceptance must not carry a physical sandbox resource.',
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
commandAccepted = true;
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (!resource) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
'Sandbox Runs requires a recorded physical resource before a remote command is accepted.',
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
const identity = resourceIdentity(resource);
|
|
321
|
+
if (!acceptedResources.has(identity)) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
`Sandbox Runs cannot accept a ${provider} command before resource ${resource.resourceId} is durably recorded.`,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
commandAccepted = true;
|
|
327
|
+
commandAcceptedResources.add(identity);
|
|
328
|
+
},
|
|
329
|
+
runnerReady: ({ provider, resource }) => {
|
|
330
|
+
const identity = resourceIdentity(resource);
|
|
331
|
+
if (
|
|
332
|
+
!acceptedResources.has(identity) ||
|
|
333
|
+
!commandAcceptedResources.has(identity)
|
|
334
|
+
) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
'Sandbox Runs cannot record detached-runner readiness before command acceptance and a durable resource fact.',
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
if (!commandAccepted) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
'Sandbox Runs cannot record detached-runner readiness before command acceptance.',
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
runnerReady = true;
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
resources: () => [...resources.values()],
|
|
348
|
+
commandAccepted: () => commandAccepted,
|
|
349
|
+
runnerReady: () => runnerReady,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function normalizeExecutionOutcome<RunnerResult>(
|
|
354
|
+
outcome: SandboxExecutionPortOutcome<RunnerResult>,
|
|
355
|
+
state: {
|
|
356
|
+
resources(): readonly SandboxExecutionResource[];
|
|
357
|
+
commandAccepted(): boolean;
|
|
358
|
+
runnerReady(): boolean;
|
|
359
|
+
},
|
|
360
|
+
mode: 'fresh' | 'continuation' | 'detached_observation',
|
|
361
|
+
): SandboxExecutionOutcome<RunnerResult> {
|
|
362
|
+
if (mode === 'detached_observation') {
|
|
363
|
+
if (state.commandAccepted() || state.resources().length > 0) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
'Sandbox Runs detached observation must resolve persisted runner state before any resource create or command start.',
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (outcome.kind === 'pre_code_deferred') {
|
|
370
|
+
if (state.commandAccepted() || state.resources().length > 0) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
'Sandbox Runs Adapter returned a pre-code deferral after a resource or command lifecycle boundary.',
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
return outcome;
|
|
376
|
+
}
|
|
377
|
+
if (outcome.kind === 'acquisition_unknown') return outcome;
|
|
378
|
+
if (outcome.kind === 'post_code_unknown') {
|
|
379
|
+
return { ...outcome, resources: state.resources() };
|
|
380
|
+
}
|
|
381
|
+
if (outcome.kind === 'suspended') {
|
|
382
|
+
if (outcome.requiresDetachedRunnerReadiness && !state.runnerReady()) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
'Sandbox Runs Adapter returned a suspension without scheduler-owned detached-runner readiness.',
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return { ...outcome, resources: state.resources() };
|
|
388
|
+
}
|
|
389
|
+
return { ...outcome, resources: state.resources() };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function resourceIdentity(resource: SandboxExecutionResource): string {
|
|
393
|
+
return `${resource.provider}:${resource.routingDomain ?? ''}:${resource.resourceId}`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function validateCreateIntent(
|
|
397
|
+
intent: import('./contract').SandboxCreateIntent,
|
|
398
|
+
): void {
|
|
399
|
+
required(intent.routingDomain, 'sandbox create intent routing domain');
|
|
400
|
+
for (const [key, value] of Object.entries(intent.correlation)) {
|
|
401
|
+
required(key, 'sandbox create intent correlation key');
|
|
402
|
+
required(value, 'sandbox create intent correlation value');
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function validateLifecycleEvent(
|
|
407
|
+
event: import('./contract').SandboxProviderLifecycleEvent,
|
|
408
|
+
): void {
|
|
409
|
+
if (
|
|
410
|
+
!Number.isSafeInteger(event.providerAttempt) ||
|
|
411
|
+
event.providerAttempt < 0
|
|
412
|
+
) {
|
|
413
|
+
throw new Error('Sandbox Runs requires a non-negative provider attempt.');
|
|
414
|
+
}
|
|
415
|
+
if (!Number.isFinite(event.occurredAtMs) || event.occurredAtMs < 0) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
'Sandbox Runs requires a non-negative lifecycle timestamp.',
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
if (event.resourceId !== undefined)
|
|
421
|
+
required(event.resourceId, 'sandbox lifecycle resource id');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function validateInput(input: SandboxRunInput): void {
|
|
425
|
+
required(input.attempt.runId, 'attempt run id');
|
|
426
|
+
if (!Number.isSafeInteger(input.attempt.number) || input.attempt.number < 0) {
|
|
427
|
+
throw new Error('Sandbox Runs requires a non-negative attempt number.');
|
|
428
|
+
}
|
|
429
|
+
required(input.authority.fenceId, 'attempt fence id');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function validateResource(resource: {
|
|
433
|
+
resourceId: string;
|
|
434
|
+
billingStartedAtMs: number;
|
|
435
|
+
routingDomain: string | null;
|
|
436
|
+
}): void {
|
|
437
|
+
required(resource.resourceId, 'sandbox resource id');
|
|
438
|
+
if (
|
|
439
|
+
!Number.isFinite(resource.billingStartedAtMs) ||
|
|
440
|
+
resource.billingStartedAtMs < 0
|
|
441
|
+
) {
|
|
442
|
+
throw new Error('Sandbox Runs requires a non-negative billing start time.');
|
|
443
|
+
}
|
|
444
|
+
if (resource.routingDomain !== null)
|
|
445
|
+
required(resource.routingDomain, 'sandbox routing domain');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function required(value: string, name: string): void {
|
|
449
|
+
if (!value?.trim()) {
|
|
450
|
+
throw new Error(`Sandbox Runs requires a non-empty ${name}.`);
|
|
451
|
+
}
|
|
452
|
+
}
|