@namzu/sandbox 13.0.0 → 15.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 (104) hide show
  1. package/CHANGELOG.md +1147 -0
  2. package/README.md +447 -0
  3. package/dist/backends/aci-standby-pool/index.d.ts.map +1 -1
  4. package/dist/backends/aci-standby-pool/index.js +13 -1
  5. package/dist/backends/aci-standby-pool/index.js.map +1 -1
  6. package/dist/backends/docker/index.d.ts.map +1 -1
  7. package/dist/backends/docker/index.js +19 -1
  8. package/dist/backends/docker/index.js.map +1 -1
  9. package/dist/backends/firecracker/index.d.ts.map +1 -1
  10. package/dist/backends/firecracker/index.js +12 -2
  11. package/dist/backends/firecracker/index.js.map +1 -1
  12. package/dist/backends/firecracker/protocol.d.ts +481 -8
  13. package/dist/backends/firecracker/protocol.d.ts.map +1 -1
  14. package/dist/backends/firecracker/protocol.js +136 -0
  15. package/dist/backends/firecracker/protocol.js.map +1 -1
  16. package/dist/backends/firecracker/transport.d.ts +642 -14
  17. package/dist/backends/firecracker/transport.d.ts.map +1 -1
  18. package/dist/backends/firecracker/transport.js +1307 -34
  19. package/dist/backends/firecracker/transport.js.map +1 -1
  20. package/dist/backends/kubernetes/egress-policy.d.ts +1296 -0
  21. package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -0
  22. package/dist/backends/kubernetes/egress-policy.js +2458 -0
  23. package/dist/backends/kubernetes/egress-policy.js.map +1 -0
  24. package/dist/backends/kubernetes/identity.d.ts +193 -0
  25. package/dist/backends/kubernetes/identity.d.ts.map +1 -0
  26. package/dist/backends/kubernetes/identity.js +147 -0
  27. package/dist/backends/kubernetes/identity.js.map +1 -0
  28. package/dist/backends/kubernetes/index.d.ts +1019 -0
  29. package/dist/backends/kubernetes/index.d.ts.map +1 -0
  30. package/dist/backends/kubernetes/index.js +1756 -0
  31. package/dist/backends/kubernetes/index.js.map +1 -0
  32. package/dist/backends/kubernetes/ingress-policy.d.ts +375 -0
  33. package/dist/backends/kubernetes/ingress-policy.d.ts.map +1 -0
  34. package/dist/backends/kubernetes/ingress-policy.js +1050 -0
  35. package/dist/backends/kubernetes/ingress-policy.js.map +1 -0
  36. package/dist/backends/kubernetes/k8s-client.d.ts +334 -0
  37. package/dist/backends/kubernetes/k8s-client.d.ts.map +1 -0
  38. package/dist/backends/kubernetes/k8s-client.js +553 -0
  39. package/dist/backends/kubernetes/k8s-client.js.map +1 -0
  40. package/dist/backends/kubernetes/lease.d.ts +145 -0
  41. package/dist/backends/kubernetes/lease.d.ts.map +1 -0
  42. package/dist/backends/kubernetes/lease.js +201 -0
  43. package/dist/backends/kubernetes/lease.js.map +1 -0
  44. package/dist/backends/kubernetes/objects.d.ts +702 -0
  45. package/dist/backends/kubernetes/objects.d.ts.map +1 -0
  46. package/dist/backends/kubernetes/objects.js +518 -0
  47. package/dist/backends/kubernetes/objects.js.map +1 -0
  48. package/dist/backends/kubernetes/per-sandbox-policy.d.ts +219 -0
  49. package/dist/backends/kubernetes/per-sandbox-policy.d.ts.map +1 -0
  50. package/dist/backends/kubernetes/per-sandbox-policy.js +407 -0
  51. package/dist/backends/kubernetes/per-sandbox-policy.js.map +1 -0
  52. package/dist/backends/kubernetes/privilege-probe.d.ts +136 -0
  53. package/dist/backends/kubernetes/privilege-probe.d.ts.map +1 -0
  54. package/dist/backends/kubernetes/privilege-probe.js +185 -0
  55. package/dist/backends/kubernetes/privilege-probe.js.map +1 -0
  56. package/dist/backends/kubernetes/rbac.d.ts +153 -0
  57. package/dist/backends/kubernetes/rbac.d.ts.map +1 -0
  58. package/dist/backends/kubernetes/rbac.js +177 -0
  59. package/dist/backends/kubernetes/rbac.js.map +1 -0
  60. package/dist/backends/kubernetes/sandbox.d.ts +190 -0
  61. package/dist/backends/kubernetes/sandbox.d.ts.map +1 -0
  62. package/dist/backends/kubernetes/sandbox.js +433 -0
  63. package/dist/backends/kubernetes/sandbox.js.map +1 -0
  64. package/dist/backends/kubernetes/transport.d.ts +1048 -0
  65. package/dist/backends/kubernetes/transport.d.ts.map +1 -0
  66. package/dist/backends/kubernetes/transport.js +2093 -0
  67. package/dist/backends/kubernetes/transport.js.map +1 -0
  68. package/dist/backends/kubernetes/workspace.d.ts +1512 -0
  69. package/dist/backends/kubernetes/workspace.d.ts.map +1 -0
  70. package/dist/backends/kubernetes/workspace.js +3703 -0
  71. package/dist/backends/kubernetes/workspace.js.map +1 -0
  72. package/dist/backends/remote-execution-controller.d.ts +14 -0
  73. package/dist/backends/remote-execution-controller.d.ts.map +1 -1
  74. package/dist/backends/remote-execution-controller.js.map +1 -1
  75. package/dist/index.d.ts +350 -2
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +344 -34
  78. package/dist/index.js.map +1 -1
  79. package/dist/testing/sandbox-conformance.d.ts +227 -0
  80. package/dist/testing/sandbox-conformance.d.ts.map +1 -0
  81. package/dist/testing/sandbox-conformance.js +896 -0
  82. package/dist/testing/sandbox-conformance.js.map +1 -0
  83. package/package.json +5 -4
  84. package/src/backends/aci-standby-pool/index.ts +16 -1
  85. package/src/backends/docker/index.ts +22 -1
  86. package/src/backends/firecracker/index.ts +14 -2
  87. package/src/backends/firecracker/protocol.ts +541 -6
  88. package/src/backends/firecracker/transport.ts +1687 -64
  89. package/src/backends/kubernetes/egress-policy.ts +3448 -0
  90. package/src/backends/kubernetes/identity.ts +261 -0
  91. package/src/backends/kubernetes/index.ts +2670 -0
  92. package/src/backends/kubernetes/ingress-policy.ts +1344 -0
  93. package/src/backends/kubernetes/k8s-client.ts +742 -0
  94. package/src/backends/kubernetes/lease.ts +254 -0
  95. package/src/backends/kubernetes/objects.ts +983 -0
  96. package/src/backends/kubernetes/per-sandbox-policy.ts +542 -0
  97. package/src/backends/kubernetes/privilege-probe.ts +261 -0
  98. package/src/backends/kubernetes/rbac.ts +192 -0
  99. package/src/backends/kubernetes/sandbox.ts +593 -0
  100. package/src/backends/kubernetes/transport.ts +2895 -0
  101. package/src/backends/kubernetes/workspace.ts +5640 -0
  102. package/src/backends/remote-execution-controller.ts +14 -0
  103. package/src/index.ts +838 -35
  104. package/src/testing/sandbox-conformance.ts +1202 -0
@@ -0,0 +1,433 @@
1
+ /**
2
+ * The {@link Sandbox} a kubernetes acquire hands back: the SDK contract,
3
+ * served over the guest agent's TCP transport, with the lease that keeps the
4
+ * cluster from deleting the pod out from under a long run.
5
+ *
6
+ * Split out of `index.ts` because that file is about the CONTROL plane —
7
+ * claim, poll, address, release — and this one is about the DATA plane, and
8
+ * the two are read for different reasons.
9
+ *
10
+ * ## What it implements, and what it deliberately does not
11
+ *
12
+ * Implemented: `exec` (through the shared {@link RemoteExecutionController},
13
+ * so an `AbortSignal` terminates the guest process rather than abandoning
14
+ * the wait), `writeFile`, `readFile`, `listFiles`, `walkFiles`,
15
+ * `openTerminal`, `openTcpConnection`, `destroy`.
16
+ *
17
+ * Conditionally present, on the same contract read the other way:
18
+ *
19
+ * - `setNetworkPolicy` — present on a TASK handle exactly when the backend
20
+ * was configured with `egress.perSandbox`, which is what gives this host
21
+ * an object to write (one `CiliumNetworkPolicy` per sandbox, owned by the
22
+ * claim), an admission fence bounding what it may write, and the RBAC to
23
+ * write it. Without that configuration there is still no per-running-pod
24
+ * knob to turn, so the method is ABSENT rather than present and throwing —
25
+ * the same rule, applied to a capability that now sometimes exists.
26
+ * Presence follows configuration alone, never a probe of the cluster. See
27
+ * `per-sandbox-policy.ts`.
28
+ *
29
+ * A WORKSPACE handle never carries it: `KubernetesWorkspace`'s create path
30
+ * builds its handle through `buildKubernetesSandbox` without this option,
31
+ * because it composes no per-sandbox pod label and tracks no owner uid for
32
+ * one. That is a property of the PATH and not of the configuration, so it
33
+ * is not answered by omission: `createKubernetesWorkspace` refuses a
34
+ * config carrying `egress.perSandbox` outright
35
+ * (`KubernetesWorkspacePerSandboxEgressConfigError`, before any request)
36
+ * rather than letting the option declare a capability the path never
37
+ * serves — see `workspace.ts`.
38
+ *
39
+ * Absent on purpose, because the SDK's contract says a backend that cannot
40
+ * honour an optional method must omit it rather than accept and ignore:
41
+ *
42
+ * - `spawnDetached` — the guest agent has no op that starts a process and
43
+ * returns it running. A host asking for background jobs must be told no.
44
+ *
45
+ * ## Terminals are owned
46
+ *
47
+ * `openTerminal` is only a compliant implementation if `destroy()` kills and
48
+ * awaits every terminal it returned, so open terminals are tracked and
49
+ * reaped before the object is released — the same thing the Firecracker
50
+ * backend does, for the same contract.
51
+ *
52
+ * ## Two terminal states, one `SandboxStatus`
53
+ *
54
+ * `SandboxStatus` has exactly four members and this change does not widen
55
+ * the SDK's union, so both ways a sandbox ends report `'destroyed'`. They
56
+ * are told apart by the error a later call throws:
57
+ * {@link KubernetesSandboxDestroyedError} (this host released it) and
58
+ * {@link KubernetesSandboxGoneError} (the cluster deleted it — the lease
59
+ * renewal found the object already gone).
60
+ */
61
+ import { walkFilesViaExec } from '@namzu/sdk';
62
+ import { OperationDeadline } from '../readiness.js';
63
+ import { RemoteCancellationUnknownError, } from '../remote-execution-controller.js';
64
+ import { KubernetesLeaseRenewal } from './lease.js';
65
+ /**
66
+ * How long a retirement triggered by an unconfirmed cancellation may spend
67
+ * deleting the object. Separate from every other clock on the path: the
68
+ * caller's own deadline has usually already expired by the time this runs,
69
+ * and reusing it would mean skipping teardown exactly when a command of
70
+ * unknown state is still out there.
71
+ */
72
+ const RETIREMENT_TIMEOUT_MS = 15_000;
73
+ /** Thrown by any operation on a sandbox this host already destroyed. */
74
+ export class KubernetesSandboxDestroyedError extends Error {
75
+ operation;
76
+ sandboxName;
77
+ name = 'KubernetesSandboxDestroyedError';
78
+ constructor(operation, sandboxName) {
79
+ super(`kubernetes sandbox ${sandboxName} has been destroyed; ${operation}() cannot be admitted. Acquire a new sandbox — a destroyed one's pod, Service and claim are deleted and its agent address no longer resolves.`);
80
+ this.operation = operation;
81
+ this.sandboxName = sandboxName;
82
+ }
83
+ }
84
+ /**
85
+ * Thrown by any operation on a sandbox the CLUSTER removed while this
86
+ * handle still held it — the lease renewal PATCH came back 404/410. Distinct
87
+ * from {@link KubernetesSandboxDestroyedError} because nothing this host did
88
+ * caused it: the object expired, an operator deleted it, or the controller
89
+ * reaped it, and the actionable advice is different.
90
+ */
91
+ export class KubernetesSandboxGoneError extends Error {
92
+ operation;
93
+ sandboxName;
94
+ name = 'KubernetesSandboxGoneError';
95
+ constructor(operation, sandboxName) {
96
+ super(`kubernetes sandbox ${sandboxName} no longer exists on the cluster; ${operation}() cannot be admitted. Its lease renewal found the object already deleted — it expired (spec.lifecycle.shutdownTime), an operator deleted it, or the controller reaped it. Nothing this handle can do brings it back; acquire a new sandbox.`);
97
+ this.operation = operation;
98
+ this.sandboxName = sandboxName;
99
+ }
100
+ }
101
+ function detectEnvironment() {
102
+ // The guest runs Linux; the enum describes the host-facing shape of the
103
+ // worker, not the isolation technology under it. Firecracker's guest
104
+ // reports the same for the same reason.
105
+ return 'linux-namespace';
106
+ }
107
+ /**
108
+ * Build the handle. It does NOT run the acquire-time privilege probe — that
109
+ * is `create()`'s job in `index.ts`, so that a refusal can destroy this
110
+ * object before any caller has a reference to it, and so this function stays
111
+ * usable by the workspace path that runs its own probe.
112
+ */
113
+ export function buildKubernetesSandbox(options) {
114
+ // The cluster owns this name. Preserving it verbatim as the sandbox id —
115
+ // as the Firecracker backend preserves its orchestrator's — means a log
116
+ // line carrying an id is also a `kubectl get sandbox` argument.
117
+ const id = options.name;
118
+ const transport = options.transport;
119
+ // Captured as a const so the conditional member below narrows: the method
120
+ // is on the handle if and only if this is defined, decided once, here.
121
+ const setNetworkPolicy = options.setNetworkPolicy;
122
+ let lifecycle = 'active';
123
+ let activeExecutions = 0;
124
+ let teardownPromise;
125
+ let teardownComplete = false;
126
+ let retirementPromise;
127
+ const terminals = new Set();
128
+ // No `renew` ⇒ no expiry to move ⇒ no loop. `stop()` on the undefined
129
+ // case is the caller's problem to not have, which is why every use below
130
+ // goes through `lease?.stop()`.
131
+ const renew = options.renew;
132
+ const ttlSeconds = options.ttlSeconds;
133
+ // The type above already pairs the two. This is the runtime half of the
134
+ // same rule, for a caller that reached here through a cast or from
135
+ // JavaScript: a lease stamping `now + 0` expires the moment it is written,
136
+ // and every tick would report success while the controller deleted the
137
+ // object underneath it.
138
+ if (renew !== undefined && (typeof ttlSeconds !== 'number' || ttlSeconds <= 0)) {
139
+ throw new Error(`kubernetes: sandbox ${options.name} was given a lease renewal with ttlSeconds ${String(ttlSeconds)}. A renewal re-stamps shutdownTime as now + ttlSeconds, so a zero or absent TTL stamps an expiry that has already passed and the object is reaped while the loop reports every tick a success. Pass renew and a positive ttlSeconds together, or neither — an object with no expiry (a persistent workspace) runs no renewal loop.`);
140
+ }
141
+ // `ttlSeconds === undefined` is unreachable once `renew` is defined — the
142
+ // throw above saw to that — and is written out anyway because it is what
143
+ // narrows the field to a number for the constructor below.
144
+ const lease = renew === undefined || ttlSeconds === undefined
145
+ ? undefined
146
+ : new KubernetesLeaseRenewal({
147
+ ttlSeconds,
148
+ renew,
149
+ onGone: () => {
150
+ // The object is gone; the pod behind the address went with it.
151
+ // Refuse every later call by name rather than let it dial into a
152
+ // connect timeout with nothing to explain it.
153
+ if (lifecycle === 'active')
154
+ lifecycle = 'gone';
155
+ },
156
+ ...(options.onRenewalError !== undefined
157
+ ? { onRenewalError: options.onRenewalError }
158
+ : {}),
159
+ ...(options.leaseIntervalMs !== undefined ? { intervalMs: options.leaseIntervalMs } : {}),
160
+ });
161
+ lease?.start();
162
+ const assertAdmissible = (operation) => {
163
+ if (lifecycle === 'active')
164
+ return;
165
+ if (lifecycle === 'gone')
166
+ throw new KubernetesSandboxGoneError(operation, options.name);
167
+ throw new KubernetesSandboxDestroyedError(operation, options.name);
168
+ };
169
+ const teardown = (signal) => {
170
+ if (lifecycle === 'active')
171
+ lifecycle = 'retiring';
172
+ lease?.stop();
173
+ if (teardownComplete)
174
+ return Promise.resolve();
175
+ if (teardownPromise)
176
+ return teardownPromise;
177
+ const shared = options.release(signal).then(() => {
178
+ teardownComplete = true;
179
+ lifecycle = 'destroyed';
180
+ }, (error) => {
181
+ // A failed teardown must stay retryable; keeping the rejected
182
+ // promise would answer every later destroy() with the same
183
+ // stale failure.
184
+ if (teardownPromise === shared)
185
+ teardownPromise = undefined;
186
+ throw error;
187
+ });
188
+ teardownPromise = shared;
189
+ return shared;
190
+ };
191
+ /**
192
+ * A command whose cancellation the guest could not confirm may still be
193
+ * running in that pod, so the pod stops being reusable. Retire it and
194
+ * report whether the retirement landed, on the error the caller is about
195
+ * to receive.
196
+ */
197
+ const retire = () => {
198
+ if (lifecycle === 'active')
199
+ lifecycle = 'retiring';
200
+ retirementPromise ??= new OperationDeadline(RETIREMENT_TIMEOUT_MS, `kubernetes sandbox ${options.name} retirement`)
201
+ .run(async (signal) => await teardown(signal))
202
+ .then(() => ({ accepted: true }))
203
+ .catch((error) => ({
204
+ accepted: false,
205
+ error: error instanceof Error ? error : new Error(String(error)),
206
+ }));
207
+ return retirementPromise;
208
+ };
209
+ /**
210
+ * What an unconfirmed cancellation does to THIS sandbox: retire it, or
211
+ * whatever the owner's hook decided instead — see
212
+ * {@link KubernetesSandboxBaseOptions.onUnconfirmedCancellation}.
213
+ */
214
+ const observeUnconfirmedCancellation = async (error) => {
215
+ const decide = options.onUnconfirmedCancellation;
216
+ if (decide === undefined)
217
+ return await retire();
218
+ try {
219
+ return await decide(error);
220
+ }
221
+ catch (hookError) {
222
+ // The caller is already receiving `error`; a hook that threw must
223
+ // not replace it, and must not be reported as a teardown that was
224
+ // attempted either.
225
+ return {
226
+ accepted: false,
227
+ error: hookError instanceof Error ? hookError : new Error(String(hookError)),
228
+ };
229
+ }
230
+ };
231
+ const runExecution = async (operation, run) => {
232
+ assertAdmissible(operation);
233
+ activeExecutions += 1;
234
+ try {
235
+ return await run();
236
+ }
237
+ catch (error) {
238
+ if (error instanceof RemoteCancellationUnknownError) {
239
+ error.retirement = await observeUnconfirmedCancellation(error);
240
+ }
241
+ throw error;
242
+ }
243
+ finally {
244
+ activeExecutions = Math.max(0, activeExecutions - 1);
245
+ }
246
+ };
247
+ return {
248
+ id,
249
+ get status() {
250
+ // Four members, and no new one: a cluster-side disappearance and a
251
+ // host-side destroy both read as 'destroyed' here and are told
252
+ // apart by the error a later call throws.
253
+ if (lifecycle !== 'active')
254
+ return 'destroyed';
255
+ return activeExecutions > 0 ? 'busy' : 'ready';
256
+ },
257
+ rootDir: options.rootDir,
258
+ environment: detectEnvironment(),
259
+ async exec(command, argv, opts) {
260
+ return await runExecution('exec', async () => await transport.exec(command, argv, opts));
261
+ },
262
+ /**
263
+ * Every `tcp` request dials a fresh connection, so its envelope is
264
+ * also that connection's first, not-yet-authenticated frame and is
265
+ * bounded by the guest's pre-auth frame ceiling (8 MiB by default).
266
+ * A body above it is no longer a refusal: the transport splits it
267
+ * into parts that each fit, writes them to a temporary sibling of
268
+ * the target and finishes with an atomic rename, so this method
269
+ * takes a body of any size the transport's `maxWriteFileBytes`
270
+ * admits (1 GiB by default). The named refusals that remain are
271
+ * passed through unwrapped so a caller can catch them BY CLASS:
272
+ * `AgentWriteFileTooLargeError` for a body above that bound, and
273
+ * `AgentPreauthFrameTooLargeError` for an oversized body against a
274
+ * guest too old to advertise the part protocol.
275
+ */
276
+ async writeFile(path, content) {
277
+ assertAdmissible('writeFile');
278
+ const buf = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8');
279
+ await transport.writeFile(path, buf);
280
+ },
281
+ /**
282
+ * A whole-file read is served by the guest's streamed op when the
283
+ * guest advertises it, so neither side holds the file's base64 form
284
+ * or its JSON envelope in one piece and a file of any size this
285
+ * workspace's disk holds can be read. `offset`/`length` ask for one
286
+ * slice instead; a guest too old to honour them is refused with
287
+ * `AgentReadFileStreamUnsupportedError` rather than answering with
288
+ * the whole file.
289
+ */
290
+ async readFile(path, readOptions) {
291
+ assertAdmissible('readFile');
292
+ return await transport.readFile(path, readOptions);
293
+ },
294
+ /**
295
+ * Chunks, in order, with nothing whole at either end — what a host
296
+ * draining a large output file before `destroy()` needs. The
297
+ * admissibility check runs at the call, not per chunk: a workspace
298
+ * suspended mid-stream takes its pod's connection with it, which is
299
+ * what ends the iteration.
300
+ */
301
+ readFileStream(path, readOptions) {
302
+ assertAdmissible('readFileStream');
303
+ return transport.readFileStream(path, readOptions);
304
+ },
305
+ // Present only when the backend was configured for per-sandbox egress
306
+ // — see {@link KubernetesSandboxBaseOptions.setNetworkPolicy}. The
307
+ // admissibility gate is this file's, not the writer's, so a destroyed
308
+ // or cluster-removed sandbox refuses BY NAME here, exactly as every
309
+ // other method does, rather than failing at the API server one round
310
+ // trip later.
311
+ ...(setNetworkPolicy !== undefined
312
+ ? {
313
+ setNetworkPolicy: async (policy) => {
314
+ assertAdmissible('setNetworkPolicy');
315
+ await setNetworkPolicy(policy);
316
+ },
317
+ }
318
+ : {}),
319
+ async openTerminal(terminalOptions) {
320
+ assertAdmissible('openTerminal');
321
+ const terminal = await transport.openTerminal(terminalOptions);
322
+ terminals.add(terminal);
323
+ void terminal.exited.finally(() => terminals.delete(terminal));
324
+ return terminal;
325
+ },
326
+ async openTcpConnection(connectOptions) {
327
+ assertAdmissible('openTcpConnection');
328
+ return await transport.openTcpConnection(connectOptions);
329
+ },
330
+ async listFiles(rootPath) {
331
+ return await runExecution('listFiles', async () => {
332
+ // Same wire as docker/aci/firecracker: `find -printf '%p\t%s\n'`,
333
+ // parsed line by line, with a non-zero exit (a root that does
334
+ // not exist yet) mapped to "empty" as the SDK contract asks.
335
+ const result = await transport.exec('find', [rootPath, '-type', 'f', '-printf', '%p\t%s\n']);
336
+ if (result.exitCode !== 0)
337
+ return [];
338
+ const entries = [];
339
+ for (const rawLine of result.stdout.split('\n')) {
340
+ if (!rawLine)
341
+ continue;
342
+ const tab = rawLine.indexOf('\t');
343
+ if (tab < 0)
344
+ continue;
345
+ const filePath = rawLine.slice(0, tab);
346
+ const size = Number.parseInt(rawLine.slice(tab + 1), 10);
347
+ if (!filePath || !Number.isFinite(size))
348
+ continue;
349
+ entries.push({ path: filePath, size });
350
+ }
351
+ return entries;
352
+ });
353
+ },
354
+ /**
355
+ * Bounded, lazy file discovery — the method the SDK's `glob` and
356
+ * `grep` builtins refuse a sandbox for not having.
357
+ *
358
+ * Built on {@link walkFilesViaExec}, the same host-side enumerator the
359
+ * Firecracker and docker backends use, over this transport's `exec`:
360
+ * the guest needs no new agent op, because the walk IS an execution —
361
+ * `node -e` running the SDK's own walk program and streaming one JSONL
362
+ * record per match. The guest image is `node:22-bookworm-slim` (see
363
+ * `k8s/Dockerfile`) and the agent is itself node, so node on the
364
+ * guest's PATH is a precondition of the agent existing rather than a
365
+ * new requirement this method introduces.
366
+ *
367
+ * Ownership is the same as `exec`'s, and deliberately NOT
368
+ * `runExecution`'s: that helper wraps one awaited call, and a walk is a
369
+ * sequence of them. `activeExecutions` is therefore held for the whole
370
+ * walk rather than per entry — `status` reads `busy` from the first
371
+ * `next()` to the last, never flapping between yields — and an
372
+ * unconfirmed cancellation retires this handle exactly as a failed
373
+ * `exec` cancel does, on the same error class and through the same
374
+ * `retire()`.
375
+ *
376
+ * Cancellation: `options.signal` and the consumer's own
377
+ * `iterator.return()` both abort the underlying `exec`, which sends
378
+ * the guest a `cancel-execution` and kills the walk's process group —
379
+ * so breaking out of the loop after five entries leaves nothing
380
+ * running in the pod.
381
+ */
382
+ async *walkFiles(rootPath, walkOptions) {
383
+ assertAdmissible('walkFiles');
384
+ activeExecutions += 1;
385
+ try {
386
+ yield* walkFilesViaExec(async (command, argv, execOpts) => await transport.exec(command, argv, execOpts), rootPath, walkOptions);
387
+ }
388
+ catch (error) {
389
+ // The same rule `runExecution` applies, inlined because a
390
+ // generator cannot be wrapped by it: a command whose
391
+ // cancellation the guest could not confirm may still be running
392
+ // in that pod, so the pod stops being reusable.
393
+ //
394
+ // TWO SITES, ONE RULE. This block and `runExecution`'s must
395
+ // change together — #480, which owns the unconfirmed-cancel
396
+ // rule for this backend, is the next change to both, and a
397
+ // change that lands in one of them is a bug in the other.
398
+ if (error instanceof RemoteCancellationUnknownError) {
399
+ error.retirement = await retire();
400
+ }
401
+ throw error;
402
+ }
403
+ finally {
404
+ activeExecutions = Math.max(0, activeExecutions - 1);
405
+ }
406
+ },
407
+ async destroy(destroyOptions) {
408
+ if (retirementPromise) {
409
+ const observation = await retirementPromise;
410
+ if (observation.accepted)
411
+ return;
412
+ retirementPromise = undefined;
413
+ }
414
+ // A terminal owns an interactive process tree in this pod. Stop and
415
+ // await every one before releasing the object, so the SDK's
416
+ // ownership contract is real rather than best-effort bookkeeping.
417
+ lifecycle = lifecycle === 'active' ? 'retiring' : lifecycle;
418
+ lease?.stop();
419
+ const activeTerminals = [...terminals];
420
+ for (const terminal of activeTerminals)
421
+ terminal.kill('SIGKILL');
422
+ await Promise.allSettled(activeTerminals.map((terminal) => terminal.exited));
423
+ terminals.clear();
424
+ // Deleting the claim cascades to the sandbox it adopted through the
425
+ // ownerReferences the controller re-parents on bind, so one DELETE
426
+ // retires the pod, the Service and the object. An object that is
427
+ // already gone counts as released — that is the state DELETE was
428
+ // asking for.
429
+ await teardown(destroyOptions?.signal);
430
+ },
431
+ };
432
+ }
433
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../../src/backends/kubernetes/sandbox.ts"],"names":[],"mappings":"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"}