@claxedo/sandbox-manager 0.7.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +245 -42
  2. package/dist/checkpoint-manager.d.ts +29 -4
  3. package/dist/checkpoint-manager.mjs +133 -24
  4. package/dist/command.d.ts +9 -2
  5. package/dist/command.mjs +8 -8
  6. package/dist/contract.d.ts +687 -0
  7. package/dist/driver-billing.d.ts +29 -0
  8. package/dist/driver-catalog.d.ts +43 -58
  9. package/dist/driver-catalog.mjs +192 -229
  10. package/dist/drivers/boat-client.d.ts +95 -0
  11. package/dist/drivers/boat-named-snapshots.d.ts +31 -0
  12. package/dist/drivers/boat-template.d.ts +49 -0
  13. package/dist/drivers/boat-template.mjs +1196 -0
  14. package/dist/drivers/boat.d.ts +64 -0
  15. package/dist/drivers/boat.mjs +990 -0
  16. package/dist/drivers/cloudflare.d.ts +5 -8
  17. package/dist/drivers/cloudflare.mjs +364 -213
  18. package/dist/drivers/docker.d.ts +4 -3
  19. package/dist/drivers/docker.mjs +244 -165
  20. package/dist/drivers/local-brokering.d.ts +14 -0
  21. package/dist/drivers/local-brokering.mjs +1029 -0
  22. package/dist/drivers/modal.d.ts +4 -7
  23. package/dist/drivers/modal.mjs +220 -155
  24. package/dist/drivers/transient-error.d.ts +13 -0
  25. package/dist/drivers/vercel.d.ts +21 -23
  26. package/dist/drivers/vercel.mjs +279 -169
  27. package/dist/egress-policy.d.ts +134 -0
  28. package/dist/egress-warnings.d.ts +22 -0
  29. package/dist/garbage-collect.d.ts +14 -0
  30. package/dist/hosted-network-policy.d.ts +11 -4
  31. package/dist/hosted-network-policy.mjs +221 -4
  32. package/dist/image-name.d.ts +12 -2
  33. package/dist/image-name.mjs +43 -0
  34. package/dist/image.d.ts +1 -20
  35. package/dist/image.mjs +11 -66
  36. package/dist/index.d.ts +8 -533
  37. package/dist/index.mjs +1420 -315
  38. package/dist/json.d.ts +13 -0
  39. package/dist/lease-failure.d.ts +173 -0
  40. package/dist/lease-lifecycle.d.ts +65 -0
  41. package/dist/lease-retry-wait.d.ts +8 -0
  42. package/dist/lease-start.d.ts +21 -0
  43. package/dist/lease-types.d.ts +15 -5
  44. package/dist/manager.d.ts +2 -0
  45. package/dist/runtime-env.d.ts +15 -2
  46. package/dist/runtime-env.mjs +50 -1
  47. package/dist/runtime-snapshot.d.ts +49 -0
  48. package/dist/runtime-version.d.ts +9 -0
  49. package/dist/runtime-version.mjs +2 -0
  50. package/dist/service-url-exposure.d.ts +1 -3
  51. package/dist/service-url-exposure.mjs +1 -26
  52. package/dist/start-fresh.d.ts +17 -0
  53. package/dist/start-telemetry.d.ts +37 -0
  54. package/dist/stores/memory.d.ts +1 -1
  55. package/dist/stores/memory.mjs +65 -54
  56. package/package.json +67 -81
  57. package/dist/daytona-allow-list.d.ts +0 -16
  58. package/dist/daytona-allow-list.mjs +0 -11
  59. package/dist/drivers/box.d.ts +0 -52
  60. package/dist/drivers/box.mjs +0 -499
  61. package/dist/drivers/cloudflare-egress.d.ts +0 -40
  62. package/dist/drivers/cloudflare-egress.mjs +0 -113
  63. package/dist/drivers/daytona.d.ts +0 -127
  64. package/dist/drivers/daytona.mjs +0 -541
  65. package/dist/drivers/exe.d.ts +0 -26
  66. package/dist/drivers/exe.mjs +0 -469
  67. package/dist/drivers/fetch-bridge.d.ts +0 -10
  68. package/dist/drivers/fetch-bridge.mjs +0 -111
  69. package/dist/lease-policy.d.ts +0 -48
  70. package/dist/lease-policy.mjs +0 -280
@@ -0,0 +1,687 @@
1
+ import { DEFAULT_WORKSPACE_RUNTIME_PORT } from "./constants";
2
+ import type { SandboxMachineClass, SandboxPhaseTiming, SandboxSecretBrokering, SandboxStartPhase } from "@claxedo/sandbox-contract";
3
+ export type { SandboxMachineClass, SandboxSecretBrokering } from "@claxedo/sandbox-contract";
4
+ import { type SandboxCheckpointCaptureInput, type SandboxCheckpointRestoreInput, type SandboxCheckpointResult, type SandboxStopInput } from "./checkpoint-manager";
5
+ import type { SandboxLifecycleEvent } from "./lease-lifecycle";
6
+ import type { SandboxBrokeredSecret, SandboxEgressControl, SandboxEgressUnenforcedEvent, SandboxNetworkPolicy } from "./egress-policy";
7
+ import type { SandboxLeaseFailure } from "./lease-failure";
8
+ export { DEFAULT_WORKSPACE_RUNTIME_PORT };
9
+ export type SandboxRegion = string;
10
+ export type SandboxCaptureScope = "none" | "same-resource" | "filesystem";
11
+ export type SandboxCaptureSourceBehavior = "not-applicable" | "preserved" | "stopped" | "deleted";
12
+ export type SandboxRestoreMountBehavior = "not-applicable" | "same-resource" | "new-resource";
13
+ export type SandboxPersistenceCapabilities = {
14
+ resume: "same-sandbox" | "replacement-restore";
15
+ capture: SandboxCaptureScope;
16
+ clone: boolean;
17
+ captureSource: SandboxCaptureSourceBehavior;
18
+ retention: "not-applicable" | "provider-managed" | "explicit";
19
+ restoreMount: SandboxRestoreMountBehavior;
20
+ };
21
+ export declare function validateSandboxPersistenceCapabilities(input: SandboxPersistenceCapabilities): {
22
+ valid: false;
23
+ reason: string;
24
+ } | {
25
+ reason?: undefined;
26
+ valid: true;
27
+ };
28
+ export type SandboxCheckpointReference = {
29
+ id: string;
30
+ providerReference: string;
31
+ sourceEpoch: number;
32
+ capturedAt: number;
33
+ metadata: {
34
+ scope: Exclude<SandboxCaptureScope, "none">;
35
+ sourceBehavior: Exclude<SandboxCaptureSourceBehavior, "not-applicable">;
36
+ restoreMount: Exclude<SandboxRestoreMountBehavior, "not-applicable">;
37
+ /** The runtime did not freeze before this capture: turns and writes in flight are as a crash left them. */
38
+ unfrozen?: true;
39
+ };
40
+ };
41
+ type SandboxRestoreIdentity = {
42
+ checkpointId: string;
43
+ sourceEpoch: number;
44
+ };
45
+ export type SandboxRestoreStatus = (SandboxRestoreIdentity & {
46
+ state: "pending";
47
+ requestedAt: number;
48
+ }) | (SandboxRestoreIdentity & {
49
+ state: "restoring";
50
+ requestedAt: number;
51
+ startedAt: number;
52
+ }) | (SandboxRestoreIdentity & {
53
+ state: "ready";
54
+ requestedAt: number;
55
+ startedAt: number;
56
+ completedAt: number;
57
+ }) | (SandboxRestoreIdentity & {
58
+ state: "failed";
59
+ requestedAt: number;
60
+ startedAt?: number;
61
+ failedAt: number;
62
+ error: string;
63
+ });
64
+ export type SandboxDriverMetadata = {
65
+ /** Where the sandbox driver code can execute. This is not sandbox infrastructure. */
66
+ driverRunsIn: Array<"worker" | "node" | "local">;
67
+ /** What `SandboxManager.stop()` actually does to the driver-owned resource. */
68
+ hostStopBehavior: "not-supported" | "suspends-host" | "terminates-host";
69
+ /** Whether a stopped/stale lease can resume the same driver-owned resource. */
70
+ hostResumeBehavior: "same-host" | "replacement-host";
71
+ /**
72
+ * The host itself reads its runtime's idleness and stops once the idle
73
+ * window has passed (the Cloudflare sandbox Worker). The control plane's
74
+ * provisioner reads every serving runtime over the relay for liveness, and
75
+ * stops an idle one through the driver only where this is absent.
76
+ */
77
+ hostWatchesIdle?: true;
78
+ /** How the control plane reaches the sandbox target returned by the driver. */
79
+ targetAccess: "relay" | "loopback";
80
+ /**
81
+ * How the driver can honor a `SandboxBrokeredSecret` — a credential the
82
+ * sandbox may USE for outbound requests but must NEVER be able to READ.
83
+ *
84
+ * - `"native"` — the provider brokers it on egress to the allowlisted hosts
85
+ * with no extra infrastructure of ours: Vercel firewall header
86
+ * transforms, Cloudflare outbound handlers reading
87
+ * the value from KV. The driver installs it during `ensureHost`.
88
+ * - `"none"` — no way to keep the value out of sandbox processes. The
89
+ * provider may still have an encrypted secret STORE (e.g. Modal secrets),
90
+ * but it is exposed as a readable env var, which cannot satisfy the
91
+ * never-readable contract.
92
+ */
93
+ secretBrokering: SandboxSecretBrokering;
94
+ /**
95
+ * How the driver can enforce a RESTRICTED `SandboxNetworkPolicy` — i.e.
96
+ * whether the sandbox's outbound network can actually be contained.
97
+ *
98
+ * A `SandboxNetworkPolicy` states the same allowance in up to two encodings:
99
+ * `hosts` (names) and `cidrs` (addresses). A driver contains egress if it
100
+ * enforces AT LEAST ONE encoding the policy carries and blocks everything
101
+ * else; the encodings are alternatives, not additive requirements.
102
+ *
103
+ * - `"hosts-and-cidrs"` — the provider filters by name AND by address.
104
+ * - `"hosts"` — the provider filters by hostname only (Vercel firewall).
105
+ * - `"none"` — the driver cannot express an egress allowlist. This covers
106
+ * BOTH drivers that throw when handed a restricted policy (docker, boat,
107
+ * and modal for host policy) AND — more dangerously — drivers that
108
+ * silently ignore `net` and run wide open (cloudflare, the fetch bridge).
109
+ * Modal is `"none"` even though it can cut the network entirely: a total
110
+ * blackout is not an allowlist and cannot serve a workspace that has to
111
+ * clone a repo and reach a model provider.
112
+ *
113
+ * The manager reads this to decide what the driver is handed:
114
+ * `sandboxEgressDisposition` enforces where it can and WITHHOLDS (loudly)
115
+ * where it cannot. See `SandboxEgressDisposition` for the posture and
116
+ * `public-docs/sandbox-egress.md` for the operator-facing consequence.
117
+ */
118
+ egressControl: SandboxEgressControl;
119
+ persistence: SandboxPersistenceCapabilities;
120
+ /** The machine classes the provider lets this driver choose at create; absent, the provider's one default. */
121
+ machineClasses?: readonly SandboxMachineClass[];
122
+ };
123
+ export type SandboxExposure = {
124
+ kind: "loopback";
125
+ } | {
126
+ kind: "relay";
127
+ } | {
128
+ kind: "private-network";
129
+ } | {
130
+ kind: "embedded";
131
+ };
132
+ export type SandboxLeaseStatus = "acquiring" | "ready" | "unavailable" | "stopped" | "destroyed";
133
+ export type SandboxLease = {
134
+ workspaceId: string;
135
+ homeRegion: SandboxRegion;
136
+ driver: string;
137
+ epoch: number;
138
+ routingId?: string;
139
+ status: SandboxLeaseStatus;
140
+ retryCount: number;
141
+ updatedAt: number;
142
+ createdAt: number;
143
+ sandboxId?: string;
144
+ url?: string;
145
+ hostId?: string;
146
+ driverResourceId?: string;
147
+ nextRetryAt?: number;
148
+ lastError?: string;
149
+ lastHeartbeatAt?: number;
150
+ lastActivityAt?: number;
151
+ /** When a start of this lease last failed, or its runtime last left a read unanswered. */
152
+ lastHealthFailureAt?: number;
153
+ labels?: Record<string, string>;
154
+ checkpoint?: SandboxCheckpointReference;
155
+ persistence?: SandboxPersistenceCapabilities;
156
+ restore?: SandboxRestoreStatus;
157
+ /** This epoch's start, recorded while it boots; a new epoch starts without one. */
158
+ start?: SandboxStartProgress;
159
+ /** The machine this epoch's sandbox runs on: the class its acquire named, else the one the host it resumes already has. */
160
+ machineClass?: SandboxMachineClass;
161
+ };
162
+ /**
163
+ * One lease epoch's start as far as it has been observed. Persisted because a
164
+ * start outlives the ensure call that began it: a driver that answers
165
+ * "provisioning" is polled again from another request, often another isolate.
166
+ *
167
+ * `startedAt` is this epoch's boot, the interval the epoch is metered by;
168
+ * `requestedAt`, `bootMode` and `failures` are the start the person is waiting
169
+ * on, which an epoch that retries a failed attempt or takes over a stale one
170
+ * inherits. A failure is recorded here as well as in the lease's `lastError`,
171
+ * which the next epoch's acquire and the target it then records clear.
172
+ */
173
+ export type SandboxStartProgress = {
174
+ requestedAt: number;
175
+ startedAt: number;
176
+ bootMode: SandboxBootMode;
177
+ /** When the last phase the control plane itself observed ended; the next one is timed from here. */
178
+ markedAt: number;
179
+ phases: SandboxStartPhase[];
180
+ /** The epochs of this start that failed, in order, each with the reason the lease recorded. */
181
+ failures?: SandboxStartFailure[];
182
+ };
183
+ export type SandboxStartFailure = {
184
+ epoch: number;
185
+ failedAt: number;
186
+ error: string;
187
+ };
188
+ export type SandboxEpochStart = SandboxStartProgress & {
189
+ epoch: number;
190
+ };
191
+ export type SandboxStartPhaseEvent = SandboxPhaseTiming<SandboxStartPhase> & {
192
+ /** Since the start the person is waiting on began, across the epochs it took. */
193
+ sinceStartMs: number;
194
+ workspaceId: string;
195
+ epoch: number;
196
+ driver: string;
197
+ homeRegion: SandboxRegion;
198
+ bootMode: SandboxBootMode;
199
+ labels: Record<string, string>;
200
+ repoSizeBytes?: number;
201
+ };
202
+ export type SandboxLeaseAcquireInput = {
203
+ homeRegion: SandboxRegion;
204
+ driver: string;
205
+ staleAfterMs: number;
206
+ now?: number;
207
+ /** The placement's labels, recorded on a fresh lease before the driver is asked for a resource; a resumed lease keeps its own. */
208
+ labels?: Record<string, string>;
209
+ /** The machine the workspace names now; a held host is resumed onto it, and absent, keeps the machine it has. */
210
+ machineClass?: SandboxMachineClass;
211
+ /** The start the new epoch continues, recorded on it before any driver step, so a poll from another isolate times from the same request. */
212
+ start?: SandboxStartProgress;
213
+ };
214
+ export type SandboxLeaseAcquireResult = {
215
+ acquired: true;
216
+ lease: SandboxLease;
217
+ } | {
218
+ acquired: false;
219
+ lease: SandboxLease;
220
+ retryAfterMs: number;
221
+ };
222
+ /**
223
+ * The lease whose host, placement and size the next epoch keeps, so that
224
+ * epoch resumes it: one stopped, or one whose attempt failed on a host the
225
+ * driver had already reported. A destroyed lease's host is gone, and a lease
226
+ * that never got one is placed afresh.
227
+ */
228
+ export declare function sandboxLeaseHeldHost(lease: SandboxLease | undefined): SandboxLease | undefined;
229
+ /**
230
+ * What may be said about a lease after it exists: lifecycle state, liveness,
231
+ * and the capture/restore record.
232
+ *
233
+ * Identity and labels are absent, and that absence is the whole ownership
234
+ * mechanism. `sandboxId`, `url`, `hostId`, `driverResourceId` and `labels`
235
+ * decide which provider resource every later checkpoint, snapshot, stop and
236
+ * destroy acts on; if a patch could carry them, anything that can reach a
237
+ * store — a status update, a heartbeat, a retry — could point one workspace's
238
+ * lease at another workspace's sandbox. They enter a lease only through
239
+ * `SandboxLeaseStore.recordTarget`, whose argument is a driver's resource report.
240
+ */
241
+ export type SandboxLeasePatch = Partial<Pick<SandboxLease, "status" | "retryCount" | "lastHeartbeatAt" | "lastActivityAt" | "lastHealthFailureAt">> & {
242
+ /** `null` clears the stored value; `undefined` leaves it unchanged. */
243
+ nextRetryAt?: number | null;
244
+ /** `null` clears the stored value; `undefined` leaves it unchanged. */
245
+ lastError?: string | null;
246
+ /** `null` clears the stored value; `undefined` leaves it unchanged. */
247
+ checkpoint?: SandboxCheckpointReference | null;
248
+ /** `null` clears the stored value; `undefined` leaves it unchanged. */
249
+ restore?: SandboxRestoreStatus | null;
250
+ start?: SandboxStartProgress;
251
+ /**
252
+ * When the host this write takes the epoch off was last proven serving, for
253
+ * a write that learns of its end after the fact; the boot it records ends
254
+ * there rather than at the write. Not a lease field.
255
+ */
256
+ endedAt?: number;
257
+ };
258
+ /**
259
+ * Canonical SandboxLeasePatch merge semantics shared by every lease store driver:
260
+ * absent/`undefined` keeps the current value, `null` clears it.
261
+ */
262
+ export declare function applySandboxLeasePatch(current: SandboxLease, patch: SandboxLeasePatch, updatedAt: number): SandboxLease;
263
+ /**
264
+ * A provider resource may exist before it has a runtime URL. Persisting its
265
+ * identity while acquiring lets the next ensure continue that same resource.
266
+ */
267
+ export type SandboxProvisionedTarget = {
268
+ sandboxId: string;
269
+ url?: string;
270
+ hostId: string;
271
+ driverResourceId?: string;
272
+ labels: Record<string, string>;
273
+ persistence?: SandboxPersistenceCapabilities;
274
+ };
275
+ /** The lease after its epoch failed: unavailable, a retry counted, and the reason on the lease and on the start it was on. */
276
+ export declare function applySandboxLeaseFailure(current: SandboxLease, error: string, nextRetryAt: number | undefined, failedAt: number): SandboxLease;
277
+ export declare function applySandboxProvisionedTarget(current: SandboxLease, target: SandboxProvisionedTarget, updatedAt: number): SandboxLease;
278
+ export type SandboxLeaseStore = {
279
+ acquire: (workspaceId: string, input: SandboxLeaseAcquireInput) => Promise<SandboxLeaseAcquireResult>;
280
+ /** The one writer of lease identity. Fenced on the epoch the driver was asked for. */
281
+ recordTarget: (workspaceId: string, expectedEpoch: number, target: SandboxProvisionedTarget) => Promise<SandboxLease | undefined>;
282
+ update: (workspaceId: string, expectedEpoch: number, patch: SandboxLeasePatch, expectedStatus?: SandboxLeaseStatus) => Promise<SandboxLease | undefined>;
283
+ recordFailure: (workspaceId: string, expectedEpoch: number, error: string, nextRetryAt?: number) => Promise<SandboxLease | undefined>;
284
+ release: (workspaceId: string) => Promise<void>;
285
+ get: (workspaceId: string) => Promise<SandboxLease | undefined>;
286
+ list: () => Promise<SandboxLease[]>;
287
+ };
288
+ export type SandboxBootSource = {
289
+ kind: "image";
290
+ image: string;
291
+ } | {
292
+ kind: "driver-snapshot";
293
+ snapshotId: string;
294
+ } | {
295
+ kind: "default";
296
+ };
297
+ export type SandboxSource = {
298
+ kind: "git";
299
+ repoUrl: string;
300
+ branch?: string;
301
+ } | {
302
+ kind: "empty";
303
+ };
304
+ export type SandboxDriverEnsureInput = {
305
+ /** Await before readiness polling so the manager can durably fence the resource identity. */
306
+ onResource?: (resource: Omit<SandboxProvisionedTarget, "url" | "persistence">) => Promise<void>;
307
+ /** Called by a driver whose provider machine, created or resumed, takes commands before the image is prepared on it. */
308
+ onMachineReady?: () => Promise<void>;
309
+ /** Called by a driver that sees its sandbox running from the image or snapshot before the runtime is up. */
310
+ onImageReady?: () => Promise<void>;
311
+ workspaceId: string;
312
+ hostId?: string;
313
+ homeRegion: SandboxRegion;
314
+ epoch: number;
315
+ labels: Record<string, string>;
316
+ bootSource?: SandboxBootSource;
317
+ workspaceRoot?: string;
318
+ runtimeCwd?: string;
319
+ workspaceRuntimePort?: number;
320
+ env?: Record<string, string>;
321
+ /** Brokered secrets: injected on egress, never readable inside the sandbox. */
322
+ secrets?: SandboxBrokeredSecret[];
323
+ source?: SandboxSource;
324
+ exposure?: SandboxExposure;
325
+ /** The machine to create the sandbox on; one the driver declared, or nothing for the provider's default. */
326
+ machineClass?: SandboxMachineClass;
327
+ /**
328
+ * Egress policy. Omitted means allow-all, which is only appropriate for a
329
+ * single-tenant deployment the operator already trusts.
330
+ *
331
+ * A driver can rely on this being ABSENT whenever it declares
332
+ * `egressControl: "none"`: the manager withholds the policy rather than hand
333
+ * it to a driver that would either throw on it or silently drop it. A driver
334
+ * that receives a restricted policy here is one that declared it can enforce
335
+ * it. @see SandboxEgressDisposition
336
+ */
337
+ net?: SandboxNetworkPolicy;
338
+ /** Optional snapshot/image to boot from (user-supplied or pre-built). */
339
+ snapshot?: string;
340
+ };
341
+ export type SandboxTarget = {
342
+ workspaceId?: string;
343
+ sandboxId: string;
344
+ url: string;
345
+ hostId: string;
346
+ driverResourceId?: string;
347
+ labels?: Record<string, string>;
348
+ driver?: {
349
+ id: string;
350
+ resourceId: string;
351
+ metadata?: Record<string, string>;
352
+ };
353
+ };
354
+ export type SandboxResource = Omit<SandboxTarget, "url"> & {
355
+ url?: string;
356
+ };
357
+ /** What the provider says of a host: running or booting, held but not running, or no longer had. */
358
+ export type SandboxHostState = "serving" | "stopped" | "gone";
359
+ export type SandboxSnapshotResult = {
360
+ snapshotId: string;
361
+ };
362
+ export type SandboxCommandResult = {
363
+ stdout: string;
364
+ stderr: string;
365
+ exitCode: number;
366
+ };
367
+ /** The label a target records the image its runtime booted from, when the control plane chose that image. */
368
+ export declare const SANDBOX_IMAGE_LABEL = "image";
369
+ export type SandboxDriver = {
370
+ id: string;
371
+ metadata: SandboxDriverMetadata;
372
+ /** The image this driver's next boot runs, when the control plane chooses it. */
373
+ image?: string;
374
+ ensureHost: (input: SandboxDriverEnsureInput) => Promise<SandboxTarget | {
375
+ provisioning: true;
376
+ retryAfterMs: number;
377
+ }>;
378
+ resumeHost?: (input: {
379
+ lease: SandboxLease;
380
+ ensure: SandboxDriverEnsureInput;
381
+ }) => Promise<SandboxTarget | {
382
+ provisioning: true;
383
+ retryAfterMs: number;
384
+ }>;
385
+ list?: () => Promise<SandboxTarget[]>;
386
+ touch?: (target: SandboxTarget) => Promise<void>;
387
+ /** A host whose runtime never published a URL is suspended all the same: a boot that failed on it still holds it. */
388
+ suspend?: (target: SandboxResource) => Promise<void>;
389
+ /**
390
+ * Stops the host; given `epoch`, a host that a newer lease generation already
391
+ * took over keeps running. `checkpoint` names the snapshot the lease references.
392
+ */
393
+ stop?: (target: SandboxResource & {
394
+ epoch?: number;
395
+ checkpoint?: string;
396
+ }) => Promise<void>;
397
+ destroy?: (target: SandboxResource) => Promise<void>;
398
+ /** `committed` names the snapshot the lease references now; a driver tracking uncommitted snapshots drops the rest. */
399
+ snapshot?: (target: SandboxTarget, committed?: string) => Promise<SandboxSnapshotResult>;
400
+ /** Deletes a snapshot `snapshot` returned; implemented by drivers whose snapshots outlive their sandbox. */
401
+ deleteSnapshot?: (target: SandboxResource, snapshotId: string) => Promise<void>;
402
+ inspect?: (target: SandboxTarget) => Promise<SandboxHostState>;
403
+ exec?: (target: SandboxTarget, command: string) => Promise<SandboxCommandResult>;
404
+ clone?: (target: SandboxTarget, input: {
405
+ name: string;
406
+ }) => Promise<SandboxTarget>;
407
+ };
408
+ export type SandboxManager = {
409
+ /** Takes or continues the workspace's lease and runs the driver for it until it answers. */
410
+ ensure: (workspaceId: string, input: SandboxManagerInput) => Promise<SandboxEnsureResult>;
411
+ /**
412
+ * The lease decision of `ensure` alone: `provisioning` names the epoch a
413
+ * caller that schedules its own driver work now holds and owes `provision`.
414
+ */
415
+ acquire: (workspaceId: string, input: SandboxManagerInput) => Promise<SandboxEnsureResult>;
416
+ /**
417
+ * The driver work of `ensure` for the lease `acquire` handed out at `epoch`;
418
+ * another epoch answers `runtime_lease_changed`. A lease whose attempt failed
419
+ * is not driven again: once its retry is due, the next attempt takes a fresh
420
+ * epoch, which the answer names, keeping the host the failed one holds.
421
+ */
422
+ provision: (workspaceId: string, epoch: number, input: SandboxManagerInput) => Promise<SandboxEnsureResult>;
423
+ register: (workspaceId: string, input: SandboxRuntimeSnapshotInput) => Promise<SandboxMutationResult>;
424
+ heartbeat: (workspaceId: string, input: SandboxRuntimeSnapshotInput) => Promise<SandboxMutationResult>;
425
+ target: (workspaceId: string) => Promise<SandboxTargetResult>;
426
+ touch: (workspaceId: string) => Promise<SandboxTouchResult>;
427
+ snapshot: (workspaceId: string, committed?: string) => Promise<SandboxSnapshotManagerResult>;
428
+ checkpoint: (workspaceId: string, input: SandboxCheckpointCaptureInput) => Promise<SandboxCheckpointResult>;
429
+ restore: (workspaceId: string, input: SandboxCheckpointRestoreInput) => Promise<SandboxCheckpointResult>;
430
+ stop: (workspaceId: string, input?: SandboxStopInput) => Promise<SandboxMutationResult>;
431
+ /**
432
+ * Ends the serving lease at `epoch` when the provider says its host is not
433
+ * running: `stopped` (held, not running) ends it stopped, so the next start
434
+ * resumes that host; `gone` ends it destroyed. Nothing while the host
435
+ * serves, the epoch moved on, or the driver cannot inspect its hosts. A
436
+ * host the provider says serves is the lease's heartbeat at that moment,
437
+ * whether or not its runtime answers: the host is billed until it stops,
438
+ * and its boot closes at the last such proof. Asked for a runtime that left
439
+ * a read unanswered, so a lease it does not end records that moment as its
440
+ * last health failure.
441
+ */
442
+ endIfHostDown: (workspaceId: string, epoch: number) => Promise<Exclude<SandboxHostState, "serving"> | undefined>;
443
+ destroy: (workspaceId: string) => Promise<SandboxMutationResult>;
444
+ /**
445
+ * A person's Start fresh: drops the stopped workspace's checkpoint so its
446
+ * next start boots the image and clones the repository. Refused while the
447
+ * workspace runs or a start is in flight (`workspace_running`), and for a
448
+ * driver whose checkpoint is the host itself or that keeps none
449
+ * (`start_fresh_unsupported`).
450
+ */
451
+ startFresh: (workspaceId: string) => Promise<SandboxMutationResult>;
452
+ release: (workspaceId: string) => Promise<{
453
+ released: boolean;
454
+ }>;
455
+ garbageCollect: () => Promise<SandboxGarbageCollectResult>;
456
+ list: () => Promise<SandboxLease[]>;
457
+ /** Phases the runtime timed inside its sandbox, recorded into that epoch's start once each. */
458
+ recordStartPhases: (workspaceId: string, input: SandboxStartPhasesReport) => Promise<void>;
459
+ /**
460
+ * Ends `phase` now, timed from the start's last observed phase. `notBefore`
461
+ * is when the evidence for it came into being; evidence older than the start
462
+ * belongs to an earlier one and records nothing.
463
+ */
464
+ markStartPhase: (workspaceId: string, input: {
465
+ epoch: number;
466
+ phase: SandboxStartPhase;
467
+ notBefore: number;
468
+ }) => Promise<void>;
469
+ };
470
+ export type SandboxStartPhasesReport = {
471
+ epoch: number;
472
+ phases: readonly SandboxPhaseTiming<SandboxStartPhase>[];
473
+ repoSizeBytes?: number;
474
+ };
475
+ export type SandboxManagerInput = {
476
+ homeRegion: SandboxRegion;
477
+ hostId?: string;
478
+ labels?: Record<string, string>;
479
+ bootSource?: SandboxBootSource;
480
+ workspaceRoot?: string;
481
+ runtimeCwd?: string;
482
+ workspaceRuntimePort?: number;
483
+ env?: Record<string, string>;
484
+ /** Brokered secrets: injected on egress, never readable inside the sandbox. */
485
+ secrets?: SandboxBrokeredSecret[];
486
+ /**
487
+ * Env minted for the lease the driver boots, joined to `env`: a credential
488
+ * bound to one lease generation and host can only be minted once the start
489
+ * has settled which epoch it runs, and a failed attempt's retry takes a new
490
+ * one.
491
+ */
492
+ leaseEnv?: (lease: {
493
+ epoch: number;
494
+ hostId: string;
495
+ }) => Promise<Record<string, string>>;
496
+ /** The machine the workspace chose; refused before a lease is taken when its driver declares no such class. */
497
+ machineClass?: SandboxMachineClass;
498
+ source?: SandboxSource;
499
+ exposure?: SandboxExposure;
500
+ /**
501
+ * @see SandboxDriverEnsureInput.net — enforced by a capable driver, withheld
502
+ * (with a warning) by one that declares `egressControl: "none"`.
503
+ */
504
+ net?: SandboxNetworkPolicy;
505
+ snapshot?: string;
506
+ /**
507
+ * Called by the one ensure whose acquire created the workspace's first
508
+ * lease, before its driver is asked for anything; a concurrent start that
509
+ * lost the acquire is not called.
510
+ */
511
+ onLeaseOpened?: () => void;
512
+ };
513
+ /**
514
+ * What a running sandbox may report about itself: liveness and activity.
515
+ *
516
+ * Identity — `sandboxId`, `url`, `hostId`, `driverResourceId` — is absent on
517
+ * purpose. `provision()` writes it from the driver's answer and nothing else
518
+ * does, because every later lifecycle call resolves its provider target from
519
+ * those fields: a snapshot able to restate them lets the reporting sandbox
520
+ * point this workspace's checkpoint, snapshot and destroy at a resource
521
+ * belonging to another workspace.
522
+ *
523
+ * `epoch` is required and never defaulted. Reading the lease's own epoch when
524
+ * the reporter omits one makes the fence agree with whatever it is handed.
525
+ */
526
+ export type SandboxRuntimeSnapshotInput = {
527
+ epoch: number;
528
+ /** Whether the runtime is serving. How a reporter words that is its own. */
529
+ ok: boolean;
530
+ active?: boolean;
531
+ now?: number;
532
+ };
533
+ /**
534
+ * What a provisioning cycle is actually doing, for honest progress UI.
535
+ * `restore` = booting a replacement restored from the workspace's snapshot
536
+ * (files come back), `resume` = restarting the same paused resource,
537
+ * `cold-start` = a fresh sandbox with no prior state to bring back (first
538
+ * boot, or nothing snapshot-able survived).
539
+ */
540
+ export type SandboxBootMode = "restore" | "resume" | "cold-start";
541
+ export type SandboxEnsureResult = ({
542
+ status: "ready";
543
+ } & SandboxTarget & {
544
+ epoch: number;
545
+ routingId?: string;
546
+ homeRegion: SandboxRegion;
547
+ /**
548
+ * Set when the driver call for this ensure failed on a lease that was
549
+ * already serving: the returned target predates this ensure's inputs.
550
+ * A caller that handed new brokered secrets must not record them as
551
+ * delivered — the provider edge still holds the previous set.
552
+ */
553
+ stale?: true;
554
+ }) | {
555
+ status: "provisioning";
556
+ retryAfterMs: number;
557
+ epoch: number;
558
+ homeRegion: SandboxRegion;
559
+ bootMode?: SandboxBootMode;
560
+ } | {
561
+ status: "unavailable";
562
+ retryAfterMs?: number;
563
+ error?: string;
564
+ epoch?: number;
565
+ homeRegion: SandboxRegion;
566
+ /** The start's retry budget is spent: `retryAfterMs` is the cooldown every start waits out, which nothing drives. */
567
+ exhausted?: true;
568
+ };
569
+ export type SandboxTargetResult = ({
570
+ status: "ready";
571
+ } & SandboxTarget & {
572
+ epoch: number;
573
+ routingId?: string;
574
+ homeRegion: SandboxRegion;
575
+ /** The runtime booted from an image other than the driver's current one; its next ensure boots the current one. */
576
+ imageOutdated?: true;
577
+ /** The start that brought this epoch up, as observed; the run still delivering its settings is timed from it. */
578
+ start?: SandboxEpochStart;
579
+ }) | {
580
+ status: "unavailable";
581
+ reason: string;
582
+ /**
583
+ * The lease's own lifecycle word when a lease exists (`"acquiring"`,
584
+ * `"stopped"`, `"unavailable"`, `"destroyed"`); absent when none does.
585
+ * `target()` deliberately does not collapse these — a read path needs to
586
+ * tell "a start is already in flight" apart from "nothing is running".
587
+ */
588
+ leaseStatus?: SandboxLeaseStatus;
589
+ /** Delay until the lease's own next scheduled retry, when it carries one. */
590
+ retryAfterMs?: number;
591
+ /** What the lease's last start or health report failed on, when it is unavailable for that reason. */
592
+ failure?: SandboxLeaseFailure;
593
+ /** Why a stopped lease was stopped, in a person's words, when neither a person nor idleness stopped it. */
594
+ stopReason?: string;
595
+ /** The start the lease is on while it is acquiring or waiting out a retry, as far as it has been observed. */
596
+ start?: SandboxEpochStart;
597
+ /** The epoch and host an acquiring lease is booting, once the driver has handed it a host. */
598
+ booting?: {
599
+ epoch: number;
600
+ hostId: string;
601
+ };
602
+ };
603
+ export type SandboxTouchResult = {
604
+ touched: boolean;
605
+ status: SandboxLeaseStatus | "missing";
606
+ };
607
+ /** `unsaved` is a stop that ended the machine without capturing it: the workspace's latest changes are lost, its last checkpoint kept. */
608
+ export type SandboxMutationResult = {
609
+ ok: true;
610
+ status: SandboxLeaseStatus;
611
+ unsaved?: true;
612
+ } | {
613
+ ok: false;
614
+ reason: string;
615
+ };
616
+ export type SandboxSnapshotManagerResult = ({
617
+ ok: true;
618
+ } & SandboxSnapshotResult) | {
619
+ ok: false;
620
+ reason: string;
621
+ };
622
+ export type SandboxGarbageCollectResult = {
623
+ destroyed: SandboxTarget[];
624
+ kept: SandboxTarget[];
625
+ skipped: Array<{
626
+ target: SandboxTarget;
627
+ reason: string;
628
+ }>;
629
+ failed: Array<{
630
+ target: SandboxTarget;
631
+ error: string;
632
+ }>;
633
+ /**
634
+ * Set when the driver cannot enumerate provider state, so the four arrays
635
+ * above mean "we could not look" rather than "nothing is orphaned". Absent on
636
+ * a real sweep. Callers must treat it as a FAILED sweep: four empty arrays
637
+ * reported as success is the exact defect finding B1 names.
638
+ */
639
+ listingUnsupported?: true;
640
+ /** Driver id, so an ops report can name which driver could not look. */
641
+ driver?: string;
642
+ /** Provider accounts a sweep over several keys could not open, so nothing on them was examined. */
643
+ unreachable?: Array<{
644
+ driver: string;
645
+ error: string;
646
+ }>;
647
+ };
648
+ /**
649
+ * A driver's `list()` may throw this shape to say "my backing service cannot
650
+ * enumerate", as distinct from "listing failed this time". `garbageCollect()`
651
+ * converts it into `listingUnsupported` (a loud, non-fatal capability gap);
652
+ * every other error propagates as a real sweep failure.
653
+ *
654
+ * Structural rather than an `instanceof` check on purpose: drivers are published
655
+ * as separate entry points and bundlers duplicate class identities, so a
656
+ * nominal check would silently fall through to "transient failure" in exactly
657
+ * the deployment that needs the distinction.
658
+ */
659
+ export type SandboxListingUnsupported = {
660
+ listingUnsupported: true;
661
+ };
662
+ export declare function isSandboxListingUnsupported(err: unknown): err is SandboxListingUnsupported;
663
+ export type SandboxManagerOptions = {
664
+ leaseStore: SandboxLeaseStore;
665
+ driver: SandboxDriver;
666
+ staleAfterMs?: number;
667
+ retryAfterMs?: number;
668
+ retryDelayMs?: (retryCount: number) => number;
669
+ maxRetryCount?: number;
670
+ retryCapCooldownMs?: number;
671
+ now?: () => number;
672
+ appLabel?: string;
673
+ /**
674
+ * Sink for the loud half of "enforce where we can, document where we can't".
675
+ * Called at composition time and again every time a restricted policy is
676
+ * withheld from a driver that cannot enforce it.
677
+ *
678
+ * Defaults to `console.warn(event.message)`. Pass your own to route the gap
679
+ * into a telemetry pipeline instead — but note the default is deliberately
680
+ * unconditional: an operator who never wires a sink still gets the warning.
681
+ * Pass `() => {}` only if you have another way to surface it.
682
+ */
683
+ onEgressUnenforced?: (event: SandboxEgressUnenforcedEvent) => void;
684
+ /** Receives each start phase as it ends; a sink that throws is ignored. */
685
+ onStartPhase?: (event: SandboxStartPhaseEvent) => void;
686
+ onLifecycle?: (event: SandboxLifecycleEvent) => void;
687
+ };