@namzu/sandbox 13.0.0 → 14.0.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 (67) hide show
  1. package/CHANGELOG.md +309 -0
  2. package/README.md +151 -0
  3. package/dist/backends/firecracker/protocol.d.ts +22 -0
  4. package/dist/backends/firecracker/protocol.d.ts.map +1 -1
  5. package/dist/backends/firecracker/protocol.js.map +1 -1
  6. package/dist/backends/firecracker/transport.d.ts +104 -9
  7. package/dist/backends/firecracker/transport.d.ts.map +1 -1
  8. package/dist/backends/firecracker/transport.js +139 -13
  9. package/dist/backends/firecracker/transport.js.map +1 -1
  10. package/dist/backends/kubernetes/egress-policy.d.ts +219 -0
  11. package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -0
  12. package/dist/backends/kubernetes/egress-policy.js +314 -0
  13. package/dist/backends/kubernetes/egress-policy.js.map +1 -0
  14. package/dist/backends/kubernetes/index.d.ts +374 -0
  15. package/dist/backends/kubernetes/index.d.ts.map +1 -0
  16. package/dist/backends/kubernetes/index.js +671 -0
  17. package/dist/backends/kubernetes/index.js.map +1 -0
  18. package/dist/backends/kubernetes/k8s-client.d.ts +125 -0
  19. package/dist/backends/kubernetes/k8s-client.d.ts.map +1 -0
  20. package/dist/backends/kubernetes/k8s-client.js +246 -0
  21. package/dist/backends/kubernetes/k8s-client.js.map +1 -0
  22. package/dist/backends/kubernetes/lease.d.ts +119 -0
  23. package/dist/backends/kubernetes/lease.d.ts.map +1 -0
  24. package/dist/backends/kubernetes/lease.js +151 -0
  25. package/dist/backends/kubernetes/lease.js.map +1 -0
  26. package/dist/backends/kubernetes/objects.d.ts +282 -0
  27. package/dist/backends/kubernetes/objects.d.ts.map +1 -0
  28. package/dist/backends/kubernetes/objects.js +156 -0
  29. package/dist/backends/kubernetes/objects.js.map +1 -0
  30. package/dist/backends/kubernetes/privilege-probe.d.ts +136 -0
  31. package/dist/backends/kubernetes/privilege-probe.d.ts.map +1 -0
  32. package/dist/backends/kubernetes/privilege-probe.js +185 -0
  33. package/dist/backends/kubernetes/privilege-probe.js.map +1 -0
  34. package/dist/backends/kubernetes/sandbox.d.ts +123 -0
  35. package/dist/backends/kubernetes/sandbox.d.ts.map +1 -0
  36. package/dist/backends/kubernetes/sandbox.js +299 -0
  37. package/dist/backends/kubernetes/sandbox.js.map +1 -0
  38. package/dist/backends/kubernetes/transport.d.ts +122 -0
  39. package/dist/backends/kubernetes/transport.d.ts.map +1 -0
  40. package/dist/backends/kubernetes/transport.js +197 -0
  41. package/dist/backends/kubernetes/transport.js.map +1 -0
  42. package/dist/backends/kubernetes/workspace.d.ts +381 -0
  43. package/dist/backends/kubernetes/workspace.d.ts.map +1 -0
  44. package/dist/backends/kubernetes/workspace.js +1064 -0
  45. package/dist/backends/kubernetes/workspace.js.map +1 -0
  46. package/dist/index.d.ts +132 -2
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +102 -34
  49. package/dist/index.js.map +1 -1
  50. package/dist/testing/sandbox-conformance.d.ts +193 -0
  51. package/dist/testing/sandbox-conformance.d.ts.map +1 -0
  52. package/dist/testing/sandbox-conformance.js +465 -0
  53. package/dist/testing/sandbox-conformance.js.map +1 -0
  54. package/package.json +5 -4
  55. package/src/backends/firecracker/protocol.ts +27 -0
  56. package/src/backends/firecracker/transport.ts +199 -28
  57. package/src/backends/kubernetes/egress-policy.ts +437 -0
  58. package/src/backends/kubernetes/index.ts +1012 -0
  59. package/src/backends/kubernetes/k8s-client.ts +352 -0
  60. package/src/backends/kubernetes/lease.ts +198 -0
  61. package/src/backends/kubernetes/objects.ts +363 -0
  62. package/src/backends/kubernetes/privilege-probe.ts +261 -0
  63. package/src/backends/kubernetes/sandbox.ts +395 -0
  64. package/src/backends/kubernetes/transport.ts +286 -0
  65. package/src/backends/kubernetes/workspace.ts +1386 -0
  66. package/src/index.ts +257 -35
  67. package/src/testing/sandbox-conformance.ts +667 -0
@@ -0,0 +1,381 @@
1
+ /**
2
+ * The persistent workspace: a Sandbox that keeps its disk across suspends.
3
+ *
4
+ * A task sandbox (`index.ts`) is claimed, used and deleted inside one run. A
5
+ * workspace is the opposite object: it is created once, addressed by a name
6
+ * the CALLER chooses, suspended when nobody is using it, resumed days later
7
+ * with yesterday's dependency cache and git checkout still on its disk, and
8
+ * deleted only when someone says so.
9
+ *
10
+ * ## Why this is a directly created Sandbox and never a claim
11
+ *
12
+ * `Sandbox.spec.volumeClaimTemplates` is CEL-immutable on the served CRD
13
+ * ("volumeClaimTemplates is immutable"), and a `SandboxClaim` that carries
14
+ * `spec.volumeClaimTemplates` is forced to cold-start instead of adopting a
15
+ * warm pool sandbox. So the disk has to be in the spec at creation, and the
16
+ * appealing middle road — claim a warm diskless sandbox and attach a disk to
17
+ * it — is not expressible in this API at all. A workspace is therefore a
18
+ * `Sandbox` POSTed directly, with a deterministic name, and the warm pool has
19
+ * nothing to do with it.
20
+ *
21
+ * ## Block, not a filesystem
22
+ *
23
+ * The disk must be `volumeMode: Block`, consumed through the container's
24
+ * `volumeDevices`, and this module REFUSES a template whose disk is anything
25
+ * else. Under a VM-isolating RuntimeClass a `Filesystem` PVC reaches the guest
26
+ * through a host/guest filesystem passthrough (virtio-fs), whose per-file
27
+ * overhead lands squarely on the two things a workspace does all day: walking
28
+ * a dependency tree and touching thousands of small files. Nothing FAILS; the
29
+ * workspace is merely several times slower, and no functional test can see
30
+ * that. A raw block device the guest formats and mounts itself is an ordinary
31
+ * local filesystem inside the VM. See {@link KubernetesWorkspaceDiskError}.
32
+ *
33
+ * ## No lease
34
+ *
35
+ * Every task sandbox carries `shutdownTime` + `shutdownPolicy: Delete`, so a
36
+ * host that dies mid-run costs the cluster one expiry rather than a leak, and
37
+ * its handle renews that expiry for as long as it lives. A workspace carries
38
+ * NEITHER. An expiry on a workspace is a timer that deletes a caller's files,
39
+ * and a renewal loop makes losing them conditional on a host process staying
40
+ * up — exactly backwards for an object whose whole purpose is to outlive the
41
+ * host. A workspace is explicitly managed: it goes away when
42
+ * `destroy({ deleteDisk: true })` says so, and not before.
43
+ *
44
+ * ## Suspend, resume, and what changes across one
45
+ *
46
+ * `suspend()` merge-PATCHes `spec.operatingMode: Suspended`; the controller
47
+ * deletes only the Pod and reconciles PVCs unconditionally on every pass, so
48
+ * the disk survives with the same UID. It then waits on the POD — not on the
49
+ * Sandbox's `Suspended` condition, which upstream documents as lingering True
50
+ * after a resume, and not merely on a `deletionTimestamp`, which appears
51
+ * while the guest is still running. `resume()` PATCHes it back and waits
52
+ * for a new pod — and a resumed pod keeps the sandbox's NAME while getting a
53
+ * new uid and a new IP. Both matter: the uid is the agent's bind token, and
54
+ * the IP is where the agent answers. So resume re-resolves the address, re-
55
+ * reads the uid (skipping the outgoing pod, which is still listed under the
56
+ * same name while it terminates) and rebuilds the transport. Nothing from
57
+ * before the suspend is reused.
58
+ *
59
+ * Between the two, every call refuses with
60
+ * {@link KubernetesWorkspaceSuspendedError} and issues no dial. A dial would
61
+ * be worse than useless: the address still resolves — the Service outlives
62
+ * the pod — so the call would hang until a connect timeout with nothing in
63
+ * the failure naming the suspend.
64
+ *
65
+ * ## Adoption is checked against the object, not against the caller
66
+ *
67
+ * A create that collides with an existing object of the same name ADOPTS it,
68
+ * because the deterministic name is only worth having if coming back is the
69
+ * normal path. What is adopted is then checked against the configuration: the
70
+ * block disk, the `sandbox.namzu.ai/template` pod label (the label an egress
71
+ * NetworkPolicy selects by) and `runtimeClassName` (the VM boundary). A
72
+ * standing object that disagrees with any of them is refused by name rather
73
+ * than driven — see {@link KubernetesWorkspaceMismatchError}. What is NOT
74
+ * checked, and cannot be from here, is whether somebody else is already using
75
+ * it: two host processes can hold handles to one running workspace, and the
76
+ * `destroy()` of either suspends the pod the other is executing in. A
77
+ * workspace id is a name, not a lock.
78
+ *
79
+ * ## There is no delete-compute-keep-disk verb
80
+ *
81
+ * The API has `operatingMode` and it has DELETE. Nothing in between. So
82
+ * `destroy()` with no options, or `deleteDisk: false`, SUSPENDS and leaves
83
+ * the object standing; only `destroy({ deleteDisk: true })` DELETEs the
84
+ * Sandbox, which cascades to the Pod, the Service and the PVC through
85
+ * ownerReferences. The default is the non-destructive one because `destroy()`
86
+ * is what a `finally` block calls, and a `finally` block must not be able to
87
+ * erase a workspace nobody asked to erase.
88
+ *
89
+ * The same holds for the paths nobody asked for at all. A create or resume
90
+ * that fails suspends the object and rethrows rather than cleaning it up (see
91
+ * {@link createKubernetesWorkspace}), and a pod that stops being able to say
92
+ * what happened to a command — the shared execution controller's unconfirmed
93
+ * cancellation, which retires a task sandbox by DELETING it — is retired here
94
+ * by that same suspend patch (see {@link retireSession}). Exactly one DELETE
95
+ * is reachable from this module, and it is the one `deleteDisk: true` asks
96
+ * for.
97
+ *
98
+ * ## A state is committed when the cluster confirms it, never before
99
+ *
100
+ * Both verbs are idempotent, and both are idempotent by EARLY-RETURNING on a
101
+ * state. That makes the moment a state is written the whole correctness
102
+ * question: a handle that marks itself `deleted` before its DELETE lands
103
+ * answers every later `destroy()` from that mark, so a 500 is thrown once and
104
+ * the Sandbox then stands on the cluster with nothing left that would remove
105
+ * it. The same shape on `suspend()` leaves a pod running — and billing —
106
+ * behind a handle that says it is suspended.
107
+ *
108
+ * So `suspended` is written after the patch lands AND the pod is observed
109
+ * stopped, `deleted` after the DELETE resolves (or reports the object already
110
+ * gone), and a request that fails leaves the state it found. Concurrency is
111
+ * covered the other way round, by a single flight per verb: a second caller
112
+ * arriving mid-transition awaits the one in progress instead of sending a
113
+ * second request into the gap the deferred mark opens.
114
+ */
115
+ import type { OpenTerminalOptions, Sandbox, SandboxDestroyOptions, SandboxTcpConnectOptions, SandboxTcpConnection, TerminalSession } from '@namzu/sdk';
116
+ import { type KubernetesBackendInternalConfig } from './index.js';
117
+ import { type SandboxPodTemplate, type SandboxVolumeClaimTemplate } from './objects.js';
118
+ /**
119
+ * Thrown when a workspace's `SandboxTemplate` does not describe a block disk
120
+ * this backend is willing to build a workspace on.
121
+ *
122
+ * Named, and thrown before anything is created, because every shape it
123
+ * refuses WORKS: a template with no disk produces a sandbox whose files
124
+ * vanish on the next suspend, and a `Filesystem` disk produces one that keeps
125
+ * its files and is quietly several times slower at the small-file IO a
126
+ * workspace is made of. Neither fails a functional test, so neither can be
127
+ * left to be noticed later.
128
+ */
129
+ export declare class KubernetesWorkspaceDiskError extends Error {
130
+ /** What was inspected — a `SandboxTemplate`, or an existing Sandbox. */
131
+ readonly source: string;
132
+ readonly name = "KubernetesWorkspaceDiskError";
133
+ constructor(
134
+ /** What was inspected — a `SandboxTemplate`, or an existing Sandbox. */
135
+ source: string, message: string);
136
+ }
137
+ /**
138
+ * Thrown when the Sandbox already standing under a workspace's name was not
139
+ * built the way this caller is configured to build one.
140
+ *
141
+ * Adoption is the NORMAL path — the deterministic name exists so that coming
142
+ * back to a workspace is cheap — and that is exactly why the object handed
143
+ * back is checked against the configuration rather than against the caller's
144
+ * intention. Two controls would otherwise be lost silently, and lost for as
145
+ * long as the workspace lives, which is the longest of anything this backend
146
+ * makes:
147
+ *
148
+ * - the `sandbox.namzu.ai/template` pod label, which is what a translated
149
+ * egress `NetworkPolicy`'s `podSelector` matches. A pod carrying another
150
+ * value — or none — is not selected by the policy this call just verified,
151
+ * so the boundary would report verified while covering nothing.
152
+ * - `runtimeClassName`, which is the VM boundary. The privilege probe cannot
153
+ * stand in for it: `/proc/self/status` reads the same inside a VM guest as
154
+ * it does inside an ordinary shared-kernel container.
155
+ *
156
+ * Nothing is patched to make the standing object match. Its disk may hold a
157
+ * month of the caller's files, and rewriting a live workspace's podTemplate to
158
+ * fit a new configuration is a larger decision than reattaching to it.
159
+ */
160
+ export declare class KubernetesWorkspaceMismatchError extends Error {
161
+ /** The Sandbox found standing under this workspace's name. */
162
+ readonly sandboxName: string;
163
+ /** Which configured field the standing object disagrees with. */
164
+ readonly field: 'sandboxTemplateName' | 'runtimeClassName';
165
+ /** What the configuration asked for. */
166
+ readonly expected: string;
167
+ /** What the object carries — absent when it carries nothing at all. */
168
+ readonly actual: string | undefined;
169
+ readonly name = "KubernetesWorkspaceMismatchError";
170
+ constructor(
171
+ /** The Sandbox found standing under this workspace's name. */
172
+ sandboxName: string,
173
+ /** Which configured field the standing object disagrees with. */
174
+ field: 'sandboxTemplateName' | 'runtimeClassName',
175
+ /** What the configuration asked for. */
176
+ expected: string,
177
+ /** What the object carries — absent when it carries nothing at all. */
178
+ actual: string | undefined, message: string);
179
+ }
180
+ /**
181
+ * Thrown by every operation on a workspace that is currently suspended.
182
+ *
183
+ * Distinct from {@link KubernetesSandboxDestroyedError} because the state is
184
+ * RECOVERABLE and the advice is one word: call `resume()`. Nothing is dialed
185
+ * before it is thrown.
186
+ */
187
+ export declare class KubernetesWorkspaceSuspendedError extends Error {
188
+ readonly operation: string;
189
+ readonly workspaceId: string;
190
+ readonly sandboxName: string;
191
+ readonly name = "KubernetesWorkspaceSuspendedError";
192
+ constructor(operation: string, workspaceId: string, sandboxName: string);
193
+ }
194
+ /**
195
+ * Thrown when a suspend's `operatingMode: Suspended` patch was accepted and
196
+ * the pod had still not stopped by the readiness deadline.
197
+ *
198
+ * The workspace is left in the state that is TRUE rather than the one that
199
+ * was asked for: the patch landed, so the pod is on its way out and no call
200
+ * is admitted — but the suspend is not recorded as finished, because it did
201
+ * not finish. A later `suspend()` sends the patch again and waits again
202
+ * instead of returning on a mark this one left behind, and `resume()` still
203
+ * works.
204
+ *
205
+ * Recording it as suspended here is exactly the defect this class exists to
206
+ * make impossible. The guest is still running, still holding the block device
207
+ * open and still writing to it, and "suspended" is a promise that the disk is
208
+ * quiesced — so a handle that made that promise on a wait it lost would let
209
+ * the next caller resume, or delete, a workspace mid-write.
210
+ */
211
+ export declare class KubernetesWorkspaceSuspendTimeoutError extends Error {
212
+ readonly workspaceId: string;
213
+ readonly sandboxName: string;
214
+ /** The readiness budget the wait was given, in milliseconds. */
215
+ readonly timeoutMs: number;
216
+ readonly name = "KubernetesWorkspaceSuspendTimeoutError";
217
+ constructor(workspaceId: string, sandboxName: string,
218
+ /** The readiness budget the wait was given, in milliseconds. */
219
+ timeoutMs: number);
220
+ }
221
+ /** Authority for one lifecycle transition, owned independently of the run. */
222
+ export interface KubernetesWorkspaceTransitionOptions {
223
+ readonly signal?: AbortSignal;
224
+ }
225
+ /**
226
+ * `destroy()` on a workspace, with the one field that decides whether the
227
+ * disk survives. See the module comment: the default keeps it.
228
+ */
229
+ export interface KubernetesWorkspaceDestroyOptions extends SandboxDestroyOptions {
230
+ /**
231
+ * `true` DELETEs the Sandbox, cascading to its Pod, Service and PVC —
232
+ * the caller's files are gone and nothing brings them back. Anything else,
233
+ * including the default, suspends and leaves the object standing.
234
+ */
235
+ readonly deleteDisk?: boolean;
236
+ }
237
+ /**
238
+ * A {@link Sandbox} that survives having its compute taken away.
239
+ *
240
+ * Declared here rather than on the SDK's `Sandbox`: the brief frames these as
241
+ * BACKEND capabilities, and keeping them out of `@namzu/sdk` means no new
242
+ * `SandboxStatus` member, no optional `suspend?()`/`resume?()` on the shared
243
+ * contract that every other backend would then have to answer for, and no
244
+ * `deleteDisk` field on the shared `SandboxDestroyOptions`.
245
+ */
246
+ export interface KubernetesWorkspace extends Sandbox {
247
+ /**
248
+ * True from the moment `suspend()` starts until `resume()` finishes.
249
+ *
250
+ * `status` cannot say this — `SandboxStatus` has four members and none of
251
+ * them is "suspended" — so a suspended workspace reports `destroyed`,
252
+ * which is the only member that means "cannot serve a call". This flag is
253
+ * what tells the recoverable state from the final one without widening the
254
+ * SDK's union.
255
+ */
256
+ readonly suspended: boolean;
257
+ openTerminal(options: OpenTerminalOptions): Promise<TerminalSession>;
258
+ openTcpConnection(options: SandboxTcpConnectOptions): Promise<SandboxTcpConnection>;
259
+ /**
260
+ * Give the compute back and keep the disk. Idempotent: suspending a
261
+ * workspace whose suspend has been CONFIRMED sends nothing.
262
+ *
263
+ * Resolves once the POD has actually stopped, not merely once the patch
264
+ * was accepted and not on the Sandbox's `Suspended` condition, which
265
+ * upstream leaves standing after a resume — a guest that ignores SIGTERM
266
+ * rides out its `terminationGracePeriodSeconds` first, and it is writing
267
+ * to the disk for all of it.
268
+ *
269
+ * Rejects, leaving the workspace usable and unchanged, if the cluster
270
+ * refuses the patch; rejects with {@link KubernetesWorkspaceSuspendTimeoutError},
271
+ * admitting no call, if the patch landed and the pod outlived the wait.
272
+ * Either way the next `suspend()` sends the patch again rather than
273
+ * returning on this one's word. Concurrent calls share one transition.
274
+ *
275
+ * A call already in flight when this is called is not cancelled: it fails
276
+ * at the transport when the pod goes away, rather than with the named
277
+ * suspended error, which only covers calls admitted from here on.
278
+ */
279
+ suspend(options?: KubernetesWorkspaceTransitionOptions): Promise<void>;
280
+ /**
281
+ * Take a new pod, on a new address, with a new agent token, and prove it
282
+ * is deprivileged before handing it back. Idempotent: resuming a running
283
+ * workspace sends nothing.
284
+ */
285
+ resume(options?: KubernetesWorkspaceTransitionOptions): Promise<void>;
286
+ /**
287
+ * With no options, or `deleteDisk: false`, this SUSPENDS: the disk stays
288
+ * and so does the handle, which reports `status: 'destroyed'` and
289
+ * `suspended: true` and can still be `resume()`d. Only
290
+ * `deleteDisk: true` is final.
291
+ *
292
+ * A `deleteDisk: true` whose DELETE fails REJECTS and stays retryable —
293
+ * the workspace is not recorded as deleted on a request that did not
294
+ * land, because the early return on that record is what a retry would
295
+ * hit. Concurrent calls share one DELETE.
296
+ */
297
+ destroy(options?: KubernetesWorkspaceDestroyOptions): Promise<void>;
298
+ }
299
+ export interface KubernetesWorkspaceOptions {
300
+ /**
301
+ * The caller's own stable name for this workspace. The Sandbox is named
302
+ * `namzu-ws-<workspaceId>`, deterministically, which is what makes a
303
+ * workspace reachable again from a different host process — see
304
+ * {@link workspaceSandboxName} for why the id is refused rather than
305
+ * sanitised.
306
+ */
307
+ readonly workspaceId: string;
308
+ /** Reported as `rootDir`; where the guest's entrypoint mounted the disk. */
309
+ readonly workingDirectory: string;
310
+ /**
311
+ * `SandboxTemplate` whose `podTemplate` AND `volumeClaimTemplates` this
312
+ * workspace is built from. Defaults to the backend's own
313
+ * `sandboxTemplateName`, but a deployment normally has two — a task
314
+ * template with no disk, and a workspace template with a block one.
315
+ */
316
+ readonly sandboxTemplateName?: string;
317
+ readonly signal?: AbortSignal;
318
+ }
319
+ /** Prefix every workspace Sandbox's name carries. */
320
+ export declare const WORKSPACE_NAME_PREFIX = "namzu-ws-";
321
+ /**
322
+ * The Sandbox name for a workspace id.
323
+ *
324
+ * Deterministic on purpose: it is the only way a second host process, or the
325
+ * same one tomorrow, finds the workspace again. Which is also why an id that
326
+ * does not already fit a DNS-1123 label is REFUSED rather than lowercased,
327
+ * stripped or hashed: sanitising maps two ids onto one name, and two callers
328
+ * who believe they have separate workspaces would be sharing one disk.
329
+ * Hashing would fit every id and make the name unreadable in `kubectl get
330
+ * sandbox`, which is most of what the deterministic name is for.
331
+ */
332
+ export declare function workspaceSandboxName(workspaceId: string): string;
333
+ /**
334
+ * Refuse anything that is not a block disk a container actually consumes as
335
+ * one. Every branch here describes a configuration that would work and then
336
+ * disappoint — see {@link KubernetesWorkspaceDiskError}.
337
+ */
338
+ export declare function assertBlockModeWorkspaceDisk(source: string, podTemplate: SandboxPodTemplate | undefined, volumeClaimTemplates: readonly SandboxVolumeClaimTemplate[] | undefined): void;
339
+ /**
340
+ * Refuse a standing Sandbox that was built from another `SandboxTemplate`, or
341
+ * that runs without the RuntimeClass this backend is configured for.
342
+ *
343
+ * Read off `spec.podTemplate` — the copy the controller actually runs a pod
344
+ * from — rather than off anything this process decided, because the question
345
+ * is what the POD is, not what the caller meant it to be.
346
+ *
347
+ * The template check is unconditional, `config.egress` set or not: the label
348
+ * is also how an operator reads which template an object came from, and an
349
+ * object whose label says one thing while the caller builds from another is a
350
+ * mix-up worth naming the first time it is seen rather than the first time a
351
+ * policy is switched on. See {@link KubernetesWorkspaceMismatchError}.
352
+ */
353
+ export declare function assertAdoptedWorkspaceMatchesConfig(sandboxName: string, namespace: string, podTemplate: SandboxPodTemplate | undefined, expected: {
354
+ readonly sandboxTemplateName: string;
355
+ readonly runtimeClassName?: string;
356
+ }): void;
357
+ /**
358
+ * Create the workspace, or take over the one that is already there.
359
+ *
360
+ * A second call with the same `workspaceId` ADOPTS rather than fails: the
361
+ * POST comes back 409 Conflict, and the object it collided with is this
362
+ * caller's own workspace from an earlier process. The deterministic name is
363
+ * only useful if coming back to it is the normal path. An adopted object is
364
+ * checked against the same block-disk rule a fresh one is, so a Sandbox
365
+ * standing under this name that is not a workspace is refused rather than
366
+ * used.
367
+ *
368
+ * Nothing here is ever deleted on failure. A create that gets as far as an
369
+ * existing object and then fails — a readiness timeout, a privilege probe
370
+ * refusal — SUSPENDS it and rethrows, because the object may be a workspace
371
+ * with a disk full of the caller's files and `deleteDisk` is not a decision
372
+ * a failure path gets to make. That holds even when THIS call POSTed the
373
+ * object and its disk is therefore empty: the 409 above means two processes
374
+ * can be coming up on one name at once, and the one that got the 201 deleting
375
+ * its "own" fresh object would take the disk of the one that adopted it. The
376
+ * cost is named rather than paid: a failed create can leave one suspended
377
+ * Sandbox standing, which the caller finds again under the same deterministic
378
+ * name and nothing reaps for them — see the docs page.
379
+ */
380
+ export declare function createKubernetesWorkspace(config: KubernetesBackendInternalConfig, options: KubernetesWorkspaceOptions): Promise<KubernetesWorkspace>;
381
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/backends/kubernetes/workspace.ts"],"names":[],"mappings":"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"}