@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,299 @@
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`, `openTerminal`,
15
+ * `openTcpConnection`, `destroy`.
16
+ *
17
+ * Absent on purpose, because the SDK's contract says a backend that cannot
18
+ * honour an optional method must omit it rather than accept and ignore:
19
+ *
20
+ * - `setNetworkPolicy` — egress here is a `NetworkPolicy` attached to the
21
+ * pool's `SandboxTemplate`. There is no per-running-pod knob to turn, and
22
+ * a policy accepted and not applied is worse than one never offered: the
23
+ * caller stops looking.
24
+ * - `spawnDetached` — the guest agent has no op that starts a process and
25
+ * returns it running. A host asking for background jobs must be told no.
26
+ * - `walkFiles` — not in this batch. A host that requires bounded search
27
+ * refuses an absent method, which is the honest answer today.
28
+ *
29
+ * ## Terminals are owned
30
+ *
31
+ * `openTerminal` is only a compliant implementation if `destroy()` kills and
32
+ * awaits every terminal it returned, so open terminals are tracked and
33
+ * reaped before the object is released — the same thing the Firecracker
34
+ * backend does, for the same contract.
35
+ *
36
+ * ## Two terminal states, one `SandboxStatus`
37
+ *
38
+ * `SandboxStatus` has exactly four members and this change does not widen
39
+ * the SDK's union, so both ways a sandbox ends report `'destroyed'`. They
40
+ * are told apart by the error a later call throws:
41
+ * {@link KubernetesSandboxDestroyedError} (this host released it) and
42
+ * {@link KubernetesSandboxGoneError} (the cluster deleted it — the lease
43
+ * renewal found the object already gone).
44
+ */
45
+ import { OperationDeadline } from '../readiness.js';
46
+ import { RemoteCancellationUnknownError, } from '../remote-execution-controller.js';
47
+ import { KubernetesLeaseRenewal } from './lease.js';
48
+ /**
49
+ * How long a retirement triggered by an unconfirmed cancellation may spend
50
+ * deleting the object. Separate from every other clock on the path: the
51
+ * caller's own deadline has usually already expired by the time this runs,
52
+ * and reusing it would mean skipping teardown exactly when a command of
53
+ * unknown state is still out there.
54
+ */
55
+ const RETIREMENT_TIMEOUT_MS = 15_000;
56
+ /** Thrown by any operation on a sandbox this host already destroyed. */
57
+ export class KubernetesSandboxDestroyedError extends Error {
58
+ operation;
59
+ sandboxName;
60
+ name = 'KubernetesSandboxDestroyedError';
61
+ constructor(operation, sandboxName) {
62
+ 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.`);
63
+ this.operation = operation;
64
+ this.sandboxName = sandboxName;
65
+ }
66
+ }
67
+ /**
68
+ * Thrown by any operation on a sandbox the CLUSTER removed while this
69
+ * handle still held it — the lease renewal PATCH came back 404/410. Distinct
70
+ * from {@link KubernetesSandboxDestroyedError} because nothing this host did
71
+ * caused it: the object expired, an operator deleted it, or the controller
72
+ * reaped it, and the actionable advice is different.
73
+ */
74
+ export class KubernetesSandboxGoneError extends Error {
75
+ operation;
76
+ sandboxName;
77
+ name = 'KubernetesSandboxGoneError';
78
+ constructor(operation, sandboxName) {
79
+ 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.`);
80
+ this.operation = operation;
81
+ this.sandboxName = sandboxName;
82
+ }
83
+ }
84
+ function detectEnvironment() {
85
+ // The guest runs Linux; the enum describes the host-facing shape of the
86
+ // worker, not the isolation technology under it. Firecracker's guest
87
+ // reports the same for the same reason.
88
+ return 'linux-namespace';
89
+ }
90
+ /**
91
+ * Build the handle. It does NOT run the acquire-time privilege probe — that
92
+ * is `create()`'s job in `index.ts`, so that a refusal can destroy this
93
+ * object before any caller has a reference to it, and so this function stays
94
+ * usable by the workspace path that runs its own probe.
95
+ */
96
+ export function buildKubernetesSandbox(options) {
97
+ // The cluster owns this name. Preserving it verbatim as the sandbox id —
98
+ // as the Firecracker backend preserves its orchestrator's — means a log
99
+ // line carrying an id is also a `kubectl get sandbox` argument.
100
+ const id = options.name;
101
+ const transport = options.transport;
102
+ let lifecycle = 'active';
103
+ let activeExecutions = 0;
104
+ let teardownPromise;
105
+ let teardownComplete = false;
106
+ let retirementPromise;
107
+ const terminals = new Set();
108
+ // No `renew` ⇒ no expiry to move ⇒ no loop. `stop()` on the undefined
109
+ // case is the caller's problem to not have, which is why every use below
110
+ // goes through `lease?.stop()`.
111
+ const renew = options.renew;
112
+ const ttlSeconds = options.ttlSeconds;
113
+ // The type above already pairs the two. This is the runtime half of the
114
+ // same rule, for a caller that reached here through a cast or from
115
+ // JavaScript: a lease stamping `now + 0` expires the moment it is written,
116
+ // and every tick would report success while the controller deleted the
117
+ // object underneath it.
118
+ if (renew !== undefined && (typeof ttlSeconds !== 'number' || ttlSeconds <= 0)) {
119
+ 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.`);
120
+ }
121
+ // `ttlSeconds === undefined` is unreachable once `renew` is defined — the
122
+ // throw above saw to that — and is written out anyway because it is what
123
+ // narrows the field to a number for the constructor below.
124
+ const lease = renew === undefined || ttlSeconds === undefined
125
+ ? undefined
126
+ : new KubernetesLeaseRenewal({
127
+ ttlSeconds,
128
+ renew,
129
+ onGone: () => {
130
+ // The object is gone; the pod behind the address went with it.
131
+ // Refuse every later call by name rather than let it dial into a
132
+ // connect timeout with nothing to explain it.
133
+ if (lifecycle === 'active')
134
+ lifecycle = 'gone';
135
+ },
136
+ ...(options.onRenewalError !== undefined
137
+ ? { onRenewalError: options.onRenewalError }
138
+ : {}),
139
+ ...(options.leaseIntervalMs !== undefined ? { intervalMs: options.leaseIntervalMs } : {}),
140
+ });
141
+ lease?.start();
142
+ const assertAdmissible = (operation) => {
143
+ if (lifecycle === 'active')
144
+ return;
145
+ if (lifecycle === 'gone')
146
+ throw new KubernetesSandboxGoneError(operation, options.name);
147
+ throw new KubernetesSandboxDestroyedError(operation, options.name);
148
+ };
149
+ const teardown = (signal) => {
150
+ if (lifecycle === 'active')
151
+ lifecycle = 'retiring';
152
+ lease?.stop();
153
+ if (teardownComplete)
154
+ return Promise.resolve();
155
+ if (teardownPromise)
156
+ return teardownPromise;
157
+ const shared = options.release(signal).then(() => {
158
+ teardownComplete = true;
159
+ lifecycle = 'destroyed';
160
+ }, (error) => {
161
+ // A failed teardown must stay retryable; keeping the rejected
162
+ // promise would answer every later destroy() with the same
163
+ // stale failure.
164
+ if (teardownPromise === shared)
165
+ teardownPromise = undefined;
166
+ throw error;
167
+ });
168
+ teardownPromise = shared;
169
+ return shared;
170
+ };
171
+ /**
172
+ * A command whose cancellation the guest could not confirm may still be
173
+ * running in that pod, so the pod stops being reusable. Retire it and
174
+ * report whether the retirement landed, on the error the caller is about
175
+ * to receive.
176
+ */
177
+ const retire = () => {
178
+ if (lifecycle === 'active')
179
+ lifecycle = 'retiring';
180
+ retirementPromise ??= new OperationDeadline(RETIREMENT_TIMEOUT_MS, `kubernetes sandbox ${options.name} retirement`)
181
+ .run(async (signal) => await teardown(signal))
182
+ .then(() => ({ accepted: true }))
183
+ .catch((error) => ({
184
+ accepted: false,
185
+ error: error instanceof Error ? error : new Error(String(error)),
186
+ }));
187
+ return retirementPromise;
188
+ };
189
+ const runExecution = async (operation, run) => {
190
+ assertAdmissible(operation);
191
+ activeExecutions += 1;
192
+ try {
193
+ return await run();
194
+ }
195
+ catch (error) {
196
+ if (error instanceof RemoteCancellationUnknownError) {
197
+ error.retirement = await retire();
198
+ }
199
+ throw error;
200
+ }
201
+ finally {
202
+ activeExecutions = Math.max(0, activeExecutions - 1);
203
+ }
204
+ };
205
+ return {
206
+ id,
207
+ get status() {
208
+ // Four members, and no new one: a cluster-side disappearance and a
209
+ // host-side destroy both read as 'destroyed' here and are told
210
+ // apart by the error a later call throws.
211
+ if (lifecycle !== 'active')
212
+ return 'destroyed';
213
+ return activeExecutions > 0 ? 'busy' : 'ready';
214
+ },
215
+ rootDir: options.rootDir,
216
+ environment: detectEnvironment(),
217
+ async exec(command, argv, opts) {
218
+ return await runExecution('exec', async () => await transport.exec(command, argv, opts));
219
+ },
220
+ /**
221
+ * Every `tcp` request dials a fresh connection, so its envelope is
222
+ * also that connection's first, not-yet-authenticated frame and is
223
+ * bounded by the guest's pre-auth frame ceiling (8 MiB by default).
224
+ * The transport checks that BEFORE dialing and throws
225
+ * `AgentPreauthFrameTooLargeError` naming the limit; it is passed
226
+ * through unwrapped so a caller can catch that class and chunk,
227
+ * rather than having to pattern-match a message.
228
+ */
229
+ async writeFile(path, content) {
230
+ assertAdmissible('writeFile');
231
+ const buf = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8');
232
+ await transport.writeFile(path, buf);
233
+ },
234
+ async readFile(path) {
235
+ assertAdmissible('readFile');
236
+ return await transport.readFile(path);
237
+ },
238
+ async openTerminal(terminalOptions) {
239
+ assertAdmissible('openTerminal');
240
+ const terminal = await transport.openTerminal(terminalOptions);
241
+ terminals.add(terminal);
242
+ void terminal.exited.finally(() => terminals.delete(terminal));
243
+ return terminal;
244
+ },
245
+ async openTcpConnection(connectOptions) {
246
+ assertAdmissible('openTcpConnection');
247
+ return await transport.openTcpConnection(connectOptions);
248
+ },
249
+ async listFiles(rootPath) {
250
+ return await runExecution('listFiles', async () => {
251
+ // Same wire as docker/aci/firecracker: `find -printf '%p\t%s\n'`,
252
+ // parsed line by line, with a non-zero exit (a root that does
253
+ // not exist yet) mapped to "empty" as the SDK contract asks.
254
+ const result = await transport.exec('find', [rootPath, '-type', 'f', '-printf', '%p\t%s\n']);
255
+ if (result.exitCode !== 0)
256
+ return [];
257
+ const entries = [];
258
+ for (const rawLine of result.stdout.split('\n')) {
259
+ if (!rawLine)
260
+ continue;
261
+ const tab = rawLine.indexOf('\t');
262
+ if (tab < 0)
263
+ continue;
264
+ const filePath = rawLine.slice(0, tab);
265
+ const size = Number.parseInt(rawLine.slice(tab + 1), 10);
266
+ if (!filePath || !Number.isFinite(size))
267
+ continue;
268
+ entries.push({ path: filePath, size });
269
+ }
270
+ return entries;
271
+ });
272
+ },
273
+ async destroy(destroyOptions) {
274
+ if (retirementPromise) {
275
+ const observation = await retirementPromise;
276
+ if (observation.accepted)
277
+ return;
278
+ retirementPromise = undefined;
279
+ }
280
+ // A terminal owns an interactive process tree in this pod. Stop and
281
+ // await every one before releasing the object, so the SDK's
282
+ // ownership contract is real rather than best-effort bookkeeping.
283
+ lifecycle = lifecycle === 'active' ? 'retiring' : lifecycle;
284
+ lease?.stop();
285
+ const activeTerminals = [...terminals];
286
+ for (const terminal of activeTerminals)
287
+ terminal.kill('SIGKILL');
288
+ await Promise.allSettled(activeTerminals.map((terminal) => terminal.exited));
289
+ terminals.clear();
290
+ // Deleting the claim cascades to the sandbox it adopted through the
291
+ // ownerReferences the controller re-parents on bind, so one DELETE
292
+ // retires the pod, the Service and the object. An object that is
293
+ // already gone counts as released — that is the state DELETE was
294
+ // asking for.
295
+ await teardown(destroyOptions?.signal);
296
+ },
297
+ };
298
+ }
299
+ //# 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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"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Host-side transport for the kubernetes backend's guest agent: the same
3
+ * `agent/agent.cjs` the Firecracker tier bakes into its golden image,
4
+ * reached over the pod network instead of a host-local vsock/unix socket.
5
+ *
6
+ * Every byte on the wire goes through {@link VsockAgentTransport}
7
+ * constructed with a `{ kind: 'tcp' }` handle (`../firecracker/transport.js`):
8
+ * the SAME per-call dial (fresh connection, no cached socket, no cached
9
+ * IP — a Service-FQDN host re-resolves on every call, so a resumed pod's
10
+ * new address costs nothing extra), the SAME 8-hex-length framing, and
11
+ * the SAME token-in-envelope credential. Nothing about the wire is
12
+ * reimplemented here.
13
+ *
14
+ * This module supplies its OWN {@link RemoteExecutionAdapter} to the
15
+ * shared {@link RemoteExecutionController} — a third adapter against the
16
+ * same controller, alongside `http-worker-client.ts` and
17
+ * `firecracker/transport.ts`, each of which does this independently — so
18
+ * it can report where an `exec()` call's wall time actually goes: the
19
+ * dial(s), the reserve round trip, the execute round trip, and the small
20
+ * amount of local bookkeeping after the peer's own work is done
21
+ * ("drain"). That attribution is the whole point of `onTiming`: the
22
+ * Firecracker relay path has a known, unexplained fixed cost, and the
23
+ * sub-second warm-acquire target this backend is judged against must be
24
+ * measured, not guessed at.
25
+ */
26
+ import type { OpenTerminalOptions, SandboxExecOptions, SandboxExecResult, SandboxTcpConnectOptions, SandboxTcpConnection, TerminalSession } from '@namzu/sdk';
27
+ import { type SandboxAgentHandle, type VsockTransportOptions } from '../firecracker/transport.js';
28
+ /** The one {@link SandboxAgentHandle} arm this backend ever constructs. */
29
+ export type KubernetesAgentHandle = Extract<SandboxAgentHandle, {
30
+ kind: 'tcp';
31
+ }>;
32
+ /**
33
+ * Thrown when the guest agent refuses a request because the handle's
34
+ * token does not match what the pod is bound to. Named distinctly from
35
+ * {@link RemoteProtocolError} so a caller can tell "this credential is
36
+ * wrong" (never going to succeed by retrying) from "the wire shape was
37
+ * unexpected".
38
+ */
39
+ export declare class KubernetesAgentUnauthorizedError extends Error {
40
+ constructor(message?: string);
41
+ }
42
+ /**
43
+ * One `exec()` call's wall-time breakdown. Durations are NOT a partition
44
+ * of a single total — `reserveMs` and `executeMs` each include their OWN
45
+ * dial, which is also folded into `dialMs` — this is a diagnostic
46
+ * breakdown for attribution, not an accounting identity. Never carries a
47
+ * token, a command, its arguments, or any output.
48
+ */
49
+ export interface KubernetesTransportTiming {
50
+ /** Total time spent establishing TCP connections for this call. */
51
+ readonly dialMs: number;
52
+ /** Time spent on the `reserve-execution` round trip (dial included). */
53
+ readonly reserveMs: number;
54
+ /** Time spent on the `execute` round trip (dial included). */
55
+ readonly executeMs: number;
56
+ /**
57
+ * Time between the execute round trip settling and `exec()` itself
58
+ * resolving — the shared controller's own post-execute bookkeeping
59
+ * (clearing timers, tearing down the observation race). Always small
60
+ * on the happy path; distinct from `executeMs` because it is spent
61
+ * locally, after the peer has nothing left to do.
62
+ */
63
+ readonly drainMs: number;
64
+ }
65
+ export interface KubernetesTransportOptions extends VsockTransportOptions {
66
+ /**
67
+ * Fires once per completed `exec()` call (success or failure) with
68
+ * the four phase durations above. The payload is exactly those four
69
+ * numbers — never the token, never a command, argv, or output.
70
+ */
71
+ readonly onTiming?: (timing: KubernetesTransportTiming) => void;
72
+ }
73
+ /**
74
+ * The kubernetes backend's dialable transport: a `tcp` handle plus a
75
+ * `RemoteExecutionAdapter` built from it, so `exec()` gets the same
76
+ * reserve-before-admission behaviour (cancellation, timeout ownership,
77
+ * "do not infer complete output on an ambiguous cancel") every other
78
+ * remote backend gets, while every network operation is delegated to
79
+ * {@link VsockAgentTransport} for the actual dial/frame/token work.
80
+ */
81
+ export declare class KubernetesAgentTransport {
82
+ private readonly handle;
83
+ private readonly transportOptions;
84
+ private readonly onTiming?;
85
+ /** Simple pass-through operations share one transport instance. */
86
+ private readonly wire;
87
+ constructor(handle: KubernetesAgentHandle, options?: KubernetesTransportOptions);
88
+ /** Readiness probe — never requires a token; see `protocol.ts`. */
89
+ healthz(signal?: AbortSignal): Promise<boolean>;
90
+ /** Poll until `healthz` succeeds or the timeout elapses. */
91
+ waitForReady(timeoutMs: number, pollIntervalMs: number, signal?: AbortSignal): Promise<void>;
92
+ /**
93
+ * The raw `reserve-execution` primitive, exposed directly (rather than
94
+ * only reachable as a side effect of `exec()`) so the reservation
95
+ * round trip is independently observable and testable against the
96
+ * real guest.
97
+ */
98
+ reserve(signal?: AbortSignal): Promise<unknown>;
99
+ /**
100
+ * The raw `cancel-execution` primitive, exposed for the same reason
101
+ * {@link reserve} is: it is a control request with its own refusal
102
+ * semantics, and proving those against the real guest should not require
103
+ * driving a whole cancelled `exec()` to reach it.
104
+ */
105
+ cancel(executionId: string, signal?: AbortSignal): Promise<unknown>;
106
+ writeFile(path: string, content: Buffer): Promise<void>;
107
+ readFile(path: string): Promise<Buffer>;
108
+ openTerminal(options: OpenTerminalOptions): Promise<TerminalSession>;
109
+ openTcpConnection(options: SandboxTcpConnectOptions): Promise<SandboxTcpConnection>;
110
+ /**
111
+ * Run one command through a fresh, call-scoped adapter + controller.
112
+ * Fresh per call — not shared instance state — because
113
+ * {@link VsockAgentTransport} itself carries no cross-call connection
114
+ * state (it dials fresh every time), so building one per `exec()` is
115
+ * free and makes concurrent `exec()` calls on the same
116
+ * `KubernetesAgentTransport` correctly independent: each gets its own
117
+ * `onDial` closure and its own timing accumulator, with no shared
118
+ * mutable field for two in-flight calls to race on.
119
+ */
120
+ exec(command: string, argv?: string[], opts?: SandboxExecOptions): Promise<SandboxExecResult>;
121
+ }
122
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/backends/kubernetes/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,oBAAoB,EACpB,eAAe,EACf,MAAM,YAAY,CAAA;AAGnB,OAAO,EAEN,KAAK,kBAAkB,EAEvB,KAAK,qBAAqB,EAC1B,MAAM,6BAA6B,CAAA;AAMpC,2EAA2E;AAC3E,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC,kBAAkB,EAAE;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAEhF;;;;;;GAMG;AACH,qBAAa,gCAAiC,SAAQ,KAAK;gBAEzD,OAAO,SAA8F;CAKtG;AAiCD;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACzC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,0BAA2B,SAAQ,qBAAqB;IACxE;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,yBAAyB,KAAK,IAAI,CAAA;CAC/D;AAED;;;;;;;GAOG;AACH,qBAAa,wBAAwB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;IACxD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAA6C;IACvE,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;gBAE9B,MAAM,EAAE,qBAAqB,EAAE,OAAO,GAAE,0BAA+B;IAQnF,mEAAmE;IAC7D,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrD,4DAA4D;IACtD,YAAY,CACjB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,IAAI,CAAC;IAIhB;;;;;OAKG;IACG,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrD;;;;;OAKG;IACG,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAQnE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIvC,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAIpE,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAIzF;;;;;;;;;OASG;IACG,IAAI,CACT,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,EAAE,EACf,IAAI,CAAC,EAAE,kBAAkB,GACvB,OAAO,CAAC,iBAAiB,CAAC;CAgE7B"}
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Host-side transport for the kubernetes backend's guest agent: the same
3
+ * `agent/agent.cjs` the Firecracker tier bakes into its golden image,
4
+ * reached over the pod network instead of a host-local vsock/unix socket.
5
+ *
6
+ * Every byte on the wire goes through {@link VsockAgentTransport}
7
+ * constructed with a `{ kind: 'tcp' }` handle (`../firecracker/transport.js`):
8
+ * the SAME per-call dial (fresh connection, no cached socket, no cached
9
+ * IP — a Service-FQDN host re-resolves on every call, so a resumed pod's
10
+ * new address costs nothing extra), the SAME 8-hex-length framing, and
11
+ * the SAME token-in-envelope credential. Nothing about the wire is
12
+ * reimplemented here.
13
+ *
14
+ * This module supplies its OWN {@link RemoteExecutionAdapter} to the
15
+ * shared {@link RemoteExecutionController} — a third adapter against the
16
+ * same controller, alongside `http-worker-client.ts` and
17
+ * `firecracker/transport.ts`, each of which does this independently — so
18
+ * it can report where an `exec()` call's wall time actually goes: the
19
+ * dial(s), the reserve round trip, the execute round trip, and the small
20
+ * amount of local bookkeeping after the peer's own work is done
21
+ * ("drain"). That attribution is the whole point of `onTiming`: the
22
+ * Firecracker relay path has a known, unexplained fixed cost, and the
23
+ * sub-second warm-acquire target this backend is judged against must be
24
+ * measured, not guessed at.
25
+ */
26
+ import { VsockAgentTransport, } from '../firecracker/transport.js';
27
+ import { RemoteExecutionController, } from '../remote-execution-controller.js';
28
+ /**
29
+ * Thrown when the guest agent refuses a request because the handle's
30
+ * token does not match what the pod is bound to. Named distinctly from
31
+ * {@link RemoteProtocolError} so a caller can tell "this credential is
32
+ * wrong" (never going to succeed by retrying) from "the wire shape was
33
+ * unexpected".
34
+ */
35
+ export class KubernetesAgentUnauthorizedError extends Error {
36
+ constructor(message = 'kubernetes tcp transport: the guest agent rejected this connection’s token (unauthorized)') {
37
+ super(message);
38
+ this.name = 'KubernetesAgentUnauthorizedError';
39
+ }
40
+ }
41
+ /** True for the wire shape `agent.cjs` sends when a token is rejected. */
42
+ function isUnauthorized(response) {
43
+ if (!response || typeof response !== 'object')
44
+ return false;
45
+ const value = response;
46
+ return value.ok === false && value.error === 'unauthorized';
47
+ }
48
+ /**
49
+ * One framed control request, with the guest's `unauthorized` refusal
50
+ * turned into {@link KubernetesAgentUnauthorizedError}.
51
+ *
52
+ * BOTH control requests go through here — `reserve-execution` and
53
+ * `cancel-execution` — because the two are read by the same caller for
54
+ * opposite reasons and neither may mistake a refusal for a blip. The
55
+ * cancel path is the sharper of the two: {@link RemoteExecutionController}
56
+ * RETRIES a failed cancellation for its whole confirm window and then
57
+ * reports the cancellation UNCONFIRMED, which retires the sandbox. A
58
+ * rejected token read as a transport failure would therefore spend that
59
+ * window re-sending a request that can never succeed, and end by
60
+ * describing a wrong credential as an ambiguous outcome.
61
+ */
62
+ async function requestChecked(wire, request, signal) {
63
+ const response = await wire.request(request, signal);
64
+ if (isUnauthorized(response))
65
+ throw new KubernetesAgentUnauthorizedError();
66
+ return response;
67
+ }
68
+ /**
69
+ * The kubernetes backend's dialable transport: a `tcp` handle plus a
70
+ * `RemoteExecutionAdapter` built from it, so `exec()` gets the same
71
+ * reserve-before-admission behaviour (cancellation, timeout ownership,
72
+ * "do not infer complete output on an ambiguous cancel") every other
73
+ * remote backend gets, while every network operation is delegated to
74
+ * {@link VsockAgentTransport} for the actual dial/frame/token work.
75
+ */
76
+ export class KubernetesAgentTransport {
77
+ handle;
78
+ transportOptions;
79
+ onTiming;
80
+ /** Simple pass-through operations share one transport instance. */
81
+ wire;
82
+ constructor(handle, options = {}) {
83
+ const { onTiming, ...transportOptions } = options;
84
+ this.handle = handle;
85
+ this.transportOptions = transportOptions;
86
+ this.onTiming = onTiming;
87
+ this.wire = new VsockAgentTransport(handle, transportOptions);
88
+ }
89
+ /** Readiness probe — never requires a token; see `protocol.ts`. */
90
+ async healthz(signal) {
91
+ return await this.wire.healthz(signal);
92
+ }
93
+ /** Poll until `healthz` succeeds or the timeout elapses. */
94
+ async waitForReady(timeoutMs, pollIntervalMs, signal) {
95
+ return await this.wire.waitForReady(timeoutMs, pollIntervalMs, signal);
96
+ }
97
+ /**
98
+ * The raw `reserve-execution` primitive, exposed directly (rather than
99
+ * only reachable as a side effect of `exec()`) so the reservation
100
+ * round trip is independently observable and testable against the
101
+ * real guest.
102
+ */
103
+ async reserve(signal) {
104
+ return await requestChecked(this.wire, { op: 'reserve-execution' }, signal);
105
+ }
106
+ /**
107
+ * The raw `cancel-execution` primitive, exposed for the same reason
108
+ * {@link reserve} is: it is a control request with its own refusal
109
+ * semantics, and proving those against the real guest should not require
110
+ * driving a whole cancelled `exec()` to reach it.
111
+ */
112
+ async cancel(executionId, signal) {
113
+ return await requestChecked(this.wire, { op: 'cancel-execution', body: { executionId } }, signal);
114
+ }
115
+ async writeFile(path, content) {
116
+ return await this.wire.writeFile(path, content);
117
+ }
118
+ async readFile(path) {
119
+ return await this.wire.readFile(path);
120
+ }
121
+ async openTerminal(options) {
122
+ return await this.wire.openTerminal(options);
123
+ }
124
+ async openTcpConnection(options) {
125
+ return await this.wire.openTcpConnection(options);
126
+ }
127
+ /**
128
+ * Run one command through a fresh, call-scoped adapter + controller.
129
+ * Fresh per call — not shared instance state — because
130
+ * {@link VsockAgentTransport} itself carries no cross-call connection
131
+ * state (it dials fresh every time), so building one per `exec()` is
132
+ * free and makes concurrent `exec()` calls on the same
133
+ * `KubernetesAgentTransport` correctly independent: each gets its own
134
+ * `onDial` closure and its own timing accumulator, with no shared
135
+ * mutable field for two in-flight calls to race on.
136
+ */
137
+ async exec(command, argv, opts) {
138
+ let dialMs = 0;
139
+ let reserveMs = 0;
140
+ let executeMs = 0;
141
+ let executeSettledAt = 0;
142
+ const timedWire = new VsockAgentTransport(this.handle, {
143
+ ...this.transportOptions,
144
+ onDial: (ms) => {
145
+ dialMs += ms;
146
+ },
147
+ });
148
+ const adapter = {
149
+ label: 'kubernetes pod-network agent',
150
+ reserve: async (signal) => {
151
+ const startedAt = Date.now();
152
+ try {
153
+ return await requestChecked(timedWire, { op: 'reserve-execution' }, signal);
154
+ }
155
+ finally {
156
+ reserveMs += Date.now() - startedAt;
157
+ }
158
+ },
159
+ // Checked exactly like `reserve` — see `requestChecked`.
160
+ cancel: async (executionId, signal) => await requestChecked(timedWire, { op: 'cancel-execution', body: { executionId } }, signal),
161
+ execute: async (executionId, cmd, execArgv, execOpts, signal, context) => {
162
+ const startedAt = Date.now();
163
+ try {
164
+ return await timedWire.executeStreamed({
165
+ ...(executionId ? { executionId } : {}),
166
+ command: cmd,
167
+ args: execArgv ?? [],
168
+ ...(execOpts?.cwd !== undefined ? { cwd: execOpts.cwd } : {}),
169
+ ...(execOpts?.env !== undefined ? { env: execOpts.env } : {}),
170
+ ...(execOpts?.timeout !== undefined ? { timeoutMs: execOpts.timeout } : {}),
171
+ ...(context?.stdin !== undefined ? { stdin: context.stdin } : {}),
172
+ ...(context?.maxOutputBytes !== undefined
173
+ ? { maxOutputBytes: context.maxOutputBytes }
174
+ : {}),
175
+ }, execOpts, signal);
176
+ }
177
+ finally {
178
+ executeMs += Date.now() - startedAt;
179
+ executeSettledAt = Date.now();
180
+ }
181
+ },
182
+ };
183
+ const controller = new RemoteExecutionController(adapter);
184
+ try {
185
+ return await controller.exec(command, argv, opts);
186
+ }
187
+ finally {
188
+ this.onTiming?.({
189
+ dialMs,
190
+ reserveMs,
191
+ executeMs,
192
+ drainMs: executeSettledAt > 0 ? Date.now() - executeSettledAt : 0,
193
+ });
194
+ }
195
+ }
196
+ }
197
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../src/backends/kubernetes/transport.ts"],"names":[],"mappings":"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"}