@namzu/sandbox 1.1.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 (69) hide show
  1. package/CHANGELOG.md +474 -0
  2. package/LICENSE.md +110 -0
  3. package/README.md +148 -0
  4. package/dist/backends/aci-standby-pool/index.d.ts +104 -0
  5. package/dist/backends/aci-standby-pool/index.d.ts.map +1 -0
  6. package/dist/backends/aci-standby-pool/index.js +425 -0
  7. package/dist/backends/aci-standby-pool/index.js.map +1 -0
  8. package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.d.ts +40 -0
  9. package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.d.ts.map +1 -0
  10. package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.js +157 -0
  11. package/dist/backends/docker/__tests__/leaf-permissions.smoke.test.js.map +1 -0
  12. package/dist/backends/docker/index.d.ts +118 -0
  13. package/dist/backends/docker/index.d.ts.map +1 -0
  14. package/dist/backends/docker/index.js +645 -0
  15. package/dist/backends/docker/index.js.map +1 -0
  16. package/dist/backends/firecracker/__tests__/backend.test.d.ts +13 -0
  17. package/dist/backends/firecracker/__tests__/backend.test.d.ts.map +1 -0
  18. package/dist/backends/firecracker/__tests__/backend.test.js +353 -0
  19. package/dist/backends/firecracker/__tests__/backend.test.js.map +1 -0
  20. package/dist/backends/firecracker/__tests__/control-plane-mtls.test.d.ts +19 -0
  21. package/dist/backends/firecracker/__tests__/control-plane-mtls.test.d.ts.map +1 -0
  22. package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js +201 -0
  23. package/dist/backends/firecracker/__tests__/control-plane-mtls.test.js.map +1 -0
  24. package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.d.ts +39 -0
  25. package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.d.ts.map +1 -0
  26. package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.js +149 -0
  27. package/dist/backends/firecracker/__tests__/fixtures/mtls-pki.js.map +1 -0
  28. package/dist/backends/firecracker/__tests__/protocol.test.d.ts +6 -0
  29. package/dist/backends/firecracker/__tests__/protocol.test.d.ts.map +1 -0
  30. package/dist/backends/firecracker/__tests__/protocol.test.js +77 -0
  31. package/dist/backends/firecracker/__tests__/protocol.test.js.map +1 -0
  32. package/dist/backends/firecracker/__tests__/transport.test.d.ts +20 -0
  33. package/dist/backends/firecracker/__tests__/transport.test.d.ts.map +1 -0
  34. package/dist/backends/firecracker/__tests__/transport.test.js +449 -0
  35. package/dist/backends/firecracker/__tests__/transport.test.js.map +1 -0
  36. package/dist/backends/firecracker/index.d.ts +124 -0
  37. package/dist/backends/firecracker/index.d.ts.map +1 -0
  38. package/dist/backends/firecracker/index.js +334 -0
  39. package/dist/backends/firecracker/index.js.map +1 -0
  40. package/dist/backends/firecracker/protocol.d.ts +132 -0
  41. package/dist/backends/firecracker/protocol.d.ts.map +1 -0
  42. package/dist/backends/firecracker/protocol.js +112 -0
  43. package/dist/backends/firecracker/protocol.js.map +1 -0
  44. package/dist/backends/firecracker/transport.d.ts +251 -0
  45. package/dist/backends/firecracker/transport.d.ts.map +1 -0
  46. package/dist/backends/firecracker/transport.js +524 -0
  47. package/dist/backends/firecracker/transport.js.map +1 -0
  48. package/dist/index.d.ts +611 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +376 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/index.test.d.ts +28 -0
  53. package/dist/index.test.d.ts.map +1 -0
  54. package/dist/index.test.js +670 -0
  55. package/dist/index.test.js.map +1 -0
  56. package/package.json +54 -0
  57. package/src/backends/aci-standby-pool/index.ts +602 -0
  58. package/src/backends/docker/__tests__/leaf-permissions.smoke.test.ts +169 -0
  59. package/src/backends/docker/index.ts +826 -0
  60. package/src/backends/firecracker/__tests__/backend.test.ts +418 -0
  61. package/src/backends/firecracker/__tests__/control-plane-mtls.test.ts +253 -0
  62. package/src/backends/firecracker/__tests__/fixtures/mtls-pki.ts +166 -0
  63. package/src/backends/firecracker/__tests__/protocol.test.ts +90 -0
  64. package/src/backends/firecracker/__tests__/transport.test.ts +526 -0
  65. package/src/backends/firecracker/index.ts +528 -0
  66. package/src/backends/firecracker/protocol.ts +191 -0
  67. package/src/backends/firecracker/transport.ts +667 -0
  68. package/src/index.test.ts +731 -0
  69. package/src/index.ts +930 -0
@@ -0,0 +1,334 @@
1
+ /**
2
+ * `microvm:self-hosted` (Firecracker) backend.
3
+ *
4
+ * Sibling of `docker/` and `aci-standby-pool/`: same
5
+ * {@link SandboxBackend} surface, same NDJSON exec-stream + base64
6
+ * file-IO **wire contract**, different transport and shipping
7
+ * mechanism.
8
+ *
9
+ * - docker `docker run`s a container and reaches an HTTP worker on a
10
+ * host loopback port.
11
+ * - aci PUTs an ACI container group and reaches the same HTTP worker
12
+ * over the group's IP.
13
+ * - firecracker POSTs the owned Azure **orchestrator** to CoW-resume a
14
+ * microVM off the golden snapshot, then reaches a **custom vsock
15
+ * agent** baked into the golden rootfs over the **vsock transport**
16
+ * (`transport.ts`). The wire FORMAT is identical (see `protocol.ts`);
17
+ * only the transport differs — HTTP for docker/aci, framed-over-vsock
18
+ * for FC, because across an FC snapshot resume a TCP control channel
19
+ * is dead-on-arrival while the vsock LISTEN socket survives
20
+ * (FC `snapshot-support.md`).
21
+ *
22
+ * ## Why this is a remote-copy backend (no host bind-mounts)
23
+ * A sibling process on the FC host cannot see the microVM's
24
+ * filesystem, so — exactly like ACI — the workspace is seeded by
25
+ * archive-sync over the control channel (tar.gz `writeFile` +
26
+ * in-sandbox `tar -xzf` via `exec`), driven by the Vandal-side
27
+ * lifecycle (`workspace-sync.ts`). This backend therefore exposes no
28
+ * `layout` mount rendering; it only needs the `outputs` container path
29
+ * as the workspace root, which the orchestrator returns as `rootDir`.
30
+ *
31
+ * ## Trust model
32
+ * The microVM is a hardware-virtualization (KVM) trust boundary —
33
+ * stronger than docker namespaces or ACI's managed isolation. The
34
+ * vsock control channel never traverses the guest's egress netns; the
35
+ * agent listens on a fixed AF_VSOCK port captured warm in the golden
36
+ * snapshot. Auth to the orchestrator rides a caller-supplied
37
+ * `getToken()` closure (the ACI `getArmToken` pattern) so
38
+ * `@namzu/sandbox` keeps zero Azure-SDK dependencies.
39
+ *
40
+ * ## Auth / SDK-dependency boundary
41
+ * `getToken()` mirrors ACI's `getArmToken`: the consumer's runtime
42
+ * owns Managed-Identity / federated-credential picking; this package
43
+ * only calls the closure on every orchestrator HTTP call so a
44
+ * long-running sandbox survives token rotation.
45
+ */
46
+ import https from 'node:https';
47
+ import { VsockAgentTransport } from './transport.js';
48
+ const DEFAULT_AGENT_VSOCK_PORT = 1024;
49
+ const DEFAULT_READY_TIMEOUT_MS = 60_000;
50
+ const DEFAULT_READY_POLL_MS = 250;
51
+ /**
52
+ * Build a {@link SandboxBackend} backed by the owned Firecracker
53
+ * platform. Construction is synchronous; the orchestrator POST happens
54
+ * on the first `create()`.
55
+ */
56
+ export function buildFirecrackerBackend(config) {
57
+ return {
58
+ tier: 'microvm',
59
+ name: 'firecracker',
60
+ async create(options) {
61
+ return await spawnFirecrackerSandbox(config, options);
62
+ },
63
+ };
64
+ }
65
+ async function orchestratorCall(endpoint, pathSuffix, method, getToken, body, mtls) {
66
+ const token = await getToken();
67
+ const url = `${endpoint.replace(/\/+$/, '')}${pathSuffix}`;
68
+ const payload = body !== undefined ? JSON.stringify(body) : undefined;
69
+ const headers = {
70
+ Authorization: `Bearer ${token}`,
71
+ 'content-type': 'application/json',
72
+ };
73
+ let res;
74
+ try {
75
+ // When CONTROL-plane mTLS material is injected the call dials over a
76
+ // node:https request presenting the client cert + pinning the server CA
77
+ // (the public-internet hop). Without it the EXISTING plain `fetch` path
78
+ // runs unchanged (the single-host default). node:https is used rather
79
+ // than fetch+undici-dispatcher because the package declares no undici
80
+ // dependency — node:https is always importable and needs nothing added.
81
+ res = mtls
82
+ ? await httpsOrchestratorRequest(url, method, headers, payload, mtls)
83
+ : await fetchOrchestratorRequest(url, method, headers, payload);
84
+ }
85
+ catch (err) {
86
+ const cause = err instanceof Error ? err.cause : undefined;
87
+ throw new Error(`firecracker orchestrator ${method} ${url} failed: ${err instanceof Error ? err.message : String(err)}${cause ? ` — cause: ${cause instanceof Error ? cause.message : String(cause)}` : ''}`, { cause: err });
88
+ }
89
+ if (res.status < 200 || res.status >= 300) {
90
+ throw new Error(`firecracker orchestrator ${method} ${url} → ${res.status}: ${await res.text()}`);
91
+ }
92
+ if (res.status === 204)
93
+ return undefined;
94
+ if (res.contentType.includes('application/json'))
95
+ return JSON.parse(await res.text());
96
+ return undefined;
97
+ }
98
+ /** The plain-`fetch` control-plane path — the single-host DEFAULT, unchanged. */
99
+ async function fetchOrchestratorRequest(url, method, headers, payload) {
100
+ const init = { method, headers };
101
+ if (payload !== undefined)
102
+ init.body = payload;
103
+ const res = await fetch(url, init);
104
+ return {
105
+ status: res.status,
106
+ contentType: res.headers.get('content-type') ?? '',
107
+ text: () => res.text(),
108
+ };
109
+ }
110
+ /**
111
+ * The mTLS control-plane path: a node:https request presenting the injected
112
+ * client cert and verifying the orchestrator's server cert against the injected
113
+ * CA (`rejectUnauthorized: true`). Used only when `controlPlaneMtls` is present;
114
+ * the plain-`fetch` path above stays the default for single-host.
115
+ */
116
+ function httpsOrchestratorRequest(url, method, headers, payload, mtls) {
117
+ const target = new URL(url);
118
+ return new Promise((resolve, reject) => {
119
+ const req = https.request({
120
+ protocol: target.protocol,
121
+ hostname: target.hostname,
122
+ port: target.port !== '' ? Number(target.port) : 443,
123
+ path: `${target.pathname}${target.search}`,
124
+ method,
125
+ headers,
126
+ ca: mtls.ca,
127
+ cert: mtls.cert,
128
+ key: mtls.key,
129
+ rejectUnauthorized: true,
130
+ minVersion: 'TLSv1.3',
131
+ ...(mtls.servername !== undefined ? { servername: mtls.servername } : {}),
132
+ }, (res) => {
133
+ const chunks = [];
134
+ res.on('data', (chunk) => chunks.push(chunk));
135
+ res.on('end', () => {
136
+ const ct = res.headers['content-type'];
137
+ resolve({
138
+ status: res.statusCode ?? 0,
139
+ contentType: Array.isArray(ct) ? (ct[0] ?? '') : (ct ?? ''),
140
+ text: async () => Buffer.concat(chunks).toString('utf8'),
141
+ });
142
+ });
143
+ res.on('error', reject);
144
+ });
145
+ req.on('error', reject);
146
+ if (payload !== undefined)
147
+ req.write(payload);
148
+ req.end();
149
+ });
150
+ }
151
+ // ---------------------------------------------------------------------------
152
+ // create()
153
+ // ---------------------------------------------------------------------------
154
+ async function spawnFirecrackerSandbox(config, options) {
155
+ const endpoint = config.orchestratorEndpoint;
156
+ const createBody = {
157
+ ...(config.template !== undefined ? { template: config.template } : {}),
158
+ ...(config.agentSnapshot !== undefined ? { agentSnapshot: config.agentSnapshot } : {}),
159
+ ...(options.memoryLimitMb !== undefined ? { memoryLimitMb: options.memoryLimitMb } : {}),
160
+ ...(options.maxProcesses !== undefined ? { maxProcesses: options.maxProcesses } : {}),
161
+ ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
162
+ ...(resolveEgressAllowlist(options) !== undefined
163
+ ? { egressAllowlist: resolveEgressAllowlist(options) }
164
+ : {}),
165
+ };
166
+ let created;
167
+ try {
168
+ created = await orchestratorCall(endpoint, '/sandboxes', 'POST', config.getToken, createBody, config.controlPlaneMtls);
169
+ }
170
+ catch (err) {
171
+ throw new Error(`firecracker: failed to create microVM sandbox — ${err instanceof Error ? err.message : String(err)}`, { cause: err });
172
+ }
173
+ if (!created || !created.sandboxId || !created.agent) {
174
+ throw new Error('firecracker: orchestrator create returned no sandboxId / agent handle');
175
+ }
176
+ const id = created.sandboxId;
177
+ const rootDir = created.rootDir;
178
+ // Normalise the orchestrator handle: fill the contract vsock port when a
179
+ // vsock handle omits it, and MERGE the consumer-injected mTLS material onto
180
+ // a network (`mtls`) handle so the transport can dial the relay. The
181
+ // orchestrator returns an `mtls` handle WITHOUT cert material (host/port/
182
+ // sandboxId only); the certs are injected here from `config.mtls`, never
183
+ // shipped by the control plane.
184
+ const handle = normalizeHandle(created.agent, config.agentVsockPort ?? DEFAULT_AGENT_VSOCK_PORT, config.mtls);
185
+ const transport = new VsockAgentTransport(handle, config.transport ?? {});
186
+ const destroy = async () => {
187
+ await orchestratorCall(endpoint, `/sandboxes/${encodeURIComponent(id)}:delete`, 'DELETE', config.getToken, undefined, config.controlPlaneMtls);
188
+ };
189
+ try {
190
+ // Readiness fence: the orchestrator's resume returns BEFORE the
191
+ // guest agent has reseeded entropy and re-listened on vsock. Stop
192
+ // the clock on the agent's healthz, exactly as the HTTP backends
193
+ // wait on `/healthz` — never on the orchestrator's 2xx, which
194
+ // fires before the guest runs (§5 clock semantics).
195
+ await transport.waitForReady(config.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS, config.readyPollIntervalMs ?? DEFAULT_READY_POLL_MS);
196
+ }
197
+ catch (err) {
198
+ // Best-effort orchestrator teardown so a readiness failure does not
199
+ // orphan a microVM/netns/UFFD handler (the reaper backstops, but
200
+ // surfacing the delete failure keeps the leak observable).
201
+ try {
202
+ await destroy();
203
+ }
204
+ catch {
205
+ // Preserve the readiness error as primary.
206
+ }
207
+ throw err;
208
+ }
209
+ let status = 'ready';
210
+ return {
211
+ id,
212
+ get status() {
213
+ return status;
214
+ },
215
+ rootDir,
216
+ environment: detectEnvironment(),
217
+ async exec(command, argv, opts) {
218
+ status = 'busy';
219
+ try {
220
+ return await transport.execute({
221
+ command,
222
+ args: argv ?? [],
223
+ ...(opts?.cwd !== undefined ? { cwd: opts.cwd } : {}),
224
+ ...(opts?.env !== undefined ? { env: opts.env } : {}),
225
+ ...(opts?.timeout !== undefined ? { timeoutMs: opts.timeout } : {}),
226
+ });
227
+ }
228
+ finally {
229
+ status = 'ready';
230
+ }
231
+ },
232
+ async writeFile(path, content) {
233
+ const buf = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8');
234
+ await transport.writeFile(path, buf);
235
+ },
236
+ async readFile(path) {
237
+ return await transport.readFile(path);
238
+ },
239
+ async listFiles(rootPath) {
240
+ // Same wire as docker/aci: `find -printf '%p\t%s\n'`, parse
241
+ // line-by-line, map a non-zero exit (missing root) to "empty".
242
+ const result = await transport.execute({
243
+ command: 'find',
244
+ args: [rootPath, '-type', 'f', '-printf', '%p\t%s\n'],
245
+ });
246
+ if (result.exitCode !== 0)
247
+ return [];
248
+ const entries = [];
249
+ for (const rawLine of result.stdout.split('\n')) {
250
+ if (!rawLine)
251
+ continue;
252
+ const tab = rawLine.indexOf('\t');
253
+ if (tab < 0)
254
+ continue;
255
+ const filePath = rawLine.slice(0, tab);
256
+ const size = Number.parseInt(rawLine.slice(tab + 1), 10);
257
+ if (!filePath || !Number.isFinite(size))
258
+ continue;
259
+ entries.push({ path: filePath, size });
260
+ }
261
+ return entries;
262
+ },
263
+ async destroy() {
264
+ status = 'destroyed';
265
+ // Let the orchestrator DELETE failure propagate — the
266
+ // Vandal-side lifecycle wraps this with logging, and a
267
+ // swallowed error here means orphaned microVMs (and their
268
+ // netns / UFFD handlers) pile up with no observability handle.
269
+ await destroy();
270
+ },
271
+ };
272
+ }
273
+ // ---------------------------------------------------------------------------
274
+ // Helpers
275
+ // ---------------------------------------------------------------------------
276
+ function resolveEgressAllowlist(options) {
277
+ const egress = options.egress;
278
+ if (!egress)
279
+ return undefined;
280
+ if (egress.kind === 'static')
281
+ return egress.allowedHosts;
282
+ // `deny-all` → empty allowlist (explicit). `allow-all` / `resolver`
283
+ // are resolved by the host before create when they apply; the
284
+ // backend forwards only the static, already-resolved shape. A
285
+ // resolver-shaped policy is the Vandal lifecycle's job to resolve
286
+ // upstream and pass as `static`.
287
+ if (egress.kind === 'deny-all')
288
+ return [];
289
+ return undefined;
290
+ }
291
+ /**
292
+ * Turn the orchestrator's WIRE handle into the transport handle the dialer
293
+ * uses. Two transforms:
294
+ *
295
+ * - `vsock` with a missing/<=0 port → fill the contract port.
296
+ * - `mtls` → MERGE the consumer-injected `mtls` cert material onto the
297
+ * wire handle (which carries only host/port/sandboxId). The orchestrator
298
+ * never ships cert material; without an injected `mtls` block a network
299
+ * handle cannot dial, so this throws loud rather than constructing a
300
+ * transport that would fail at connect time.
301
+ *
302
+ * `unix` + already-correct `vsock` handles pass through untouched.
303
+ */
304
+ export function normalizeHandle(handle, contractPort, mtls) {
305
+ if (handle.kind === 'mtls') {
306
+ if (!mtls) {
307
+ throw new Error('firecracker: orchestrator returned an mtls agent handle but no client cert material was injected ' +
308
+ '(the Vandal host layer must supply VANDAL_SANDBOX_FC_TLS_CA/_CERT/_KEY in network mode)');
309
+ }
310
+ return {
311
+ kind: 'mtls',
312
+ host: handle.host,
313
+ port: handle.port,
314
+ sandboxId: handle.sandboxId,
315
+ tls: {
316
+ ca: mtls.ca,
317
+ cert: mtls.cert,
318
+ key: mtls.key,
319
+ ...(mtls.servername !== undefined ? { servername: mtls.servername } : {}),
320
+ },
321
+ };
322
+ }
323
+ if (handle.kind === 'vsock' && (!handle.port || handle.port <= 0)) {
324
+ return { kind: 'vsock', udsPath: handle.udsPath, port: contractPort };
325
+ }
326
+ return handle;
327
+ }
328
+ function detectEnvironment() {
329
+ // Firecracker guests run Linux; the agent is a Linux-namespace-style
330
+ // worker from the SDK's perspective (the enum is host-shape, not
331
+ // guest internals).
332
+ return 'linux-namespace';
333
+ }
334
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/backends/firecracker/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The ONE wire contract, shared across both transports.
3
+ *
4
+ * The docker (`backends/docker/`) and ACI (`backends/aci-standby-pool/`)
5
+ * backends speak this contract over **HTTP**: a streaming NDJSON
6
+ * `/execute` response and base64-bodied `/read-file` / `/write-file`
7
+ * JSON requests, served by `worker/server.js`. The Firecracker
8
+ * backend speaks the **same NDJSON shapes and the same base64 file-IO
9
+ * shapes** — only the transport changes from HTTP-over-TCP to
10
+ * framed-over-vsock (see `transport.ts`).
11
+ *
12
+ * This module is the single source of truth for the message shapes
13
+ * and the streaming exec-line parser, so the two transports cannot
14
+ * drift. It deliberately carries NO transport concern (no `fetch`, no
15
+ * socket) — it is pure codec. The HTTP backends keep their own inline
16
+ * copies (they predate this module and stay UNTOUCHED per the P0
17
+ * scope); this module mirrors those shapes verbatim and is the
18
+ * canonical definition the vsock path consumes.
19
+ *
20
+ * Why mirror rather than refactor docker/aci onto it: the P0 scope is
21
+ * "do NOT touch docker or aci". Centralising the shapes here, with the
22
+ * inline docker copy pinned by `backends/docker/__tests__` and this
23
+ * module pinned by `backends/firecracker/__tests__`, keeps both honest
24
+ * without editing the frozen HTTP path. A later cleanup pass can fold
25
+ * docker/aci onto this module once it is no longer release-frozen.
26
+ */
27
+ import type { SandboxExecResult } from '@namzu/sdk';
28
+ /**
29
+ * `/execute` request body. Identical field set to the HTTP worker's
30
+ * `handleExecute` body (`command`, `args`, `cwd`, `env`, `stdin`,
31
+ * `timeoutMs`, `maxOutputBytes`). `timeoutMs` maps from the SDK's
32
+ * `SandboxExecOptions.timeout`.
33
+ */
34
+ export interface ExecRequest {
35
+ readonly command: string;
36
+ readonly args?: readonly string[];
37
+ readonly cwd?: string;
38
+ readonly env?: Record<string, string>;
39
+ readonly stdin?: string;
40
+ readonly timeoutMs?: number;
41
+ readonly maxOutputBytes?: number;
42
+ }
43
+ /**
44
+ * One NDJSON event the agent emits while streaming an `/execute`. The
45
+ * exact union the HTTP worker writes via `writeEvent`:
46
+ * { type: 'stdout_delta', data }
47
+ * { type: 'stderr_delta', data }
48
+ * { type: 'result', exitCode, timedOut, durationMs, stdoutTruncated?, stderrTruncated? }
49
+ * { type: 'error', error }
50
+ */
51
+ export type ExecEvent = {
52
+ readonly type: 'stdout_delta';
53
+ readonly data: string;
54
+ } | {
55
+ readonly type: 'stderr_delta';
56
+ readonly data: string;
57
+ } | {
58
+ readonly type: 'result';
59
+ readonly exitCode: number;
60
+ readonly timedOut: boolean;
61
+ readonly durationMs?: number;
62
+ readonly stdoutTruncated?: boolean;
63
+ readonly stderrTruncated?: boolean;
64
+ } | {
65
+ readonly type: 'error';
66
+ readonly error: string;
67
+ };
68
+ /** `/write-file` request body. `content` is base64. */
69
+ export interface WriteFileRequest {
70
+ readonly path: string;
71
+ readonly content: string;
72
+ readonly encoding: 'base64';
73
+ }
74
+ /** `/write-file` success response. */
75
+ export interface WriteFileResponse {
76
+ readonly ok: boolean;
77
+ readonly bytesWritten?: number;
78
+ readonly error?: string;
79
+ }
80
+ /** `/read-file` request body. */
81
+ export interface ReadFileRequest {
82
+ readonly path: string;
83
+ readonly encoding: 'base64';
84
+ }
85
+ /** `/read-file` response. `content` is base64 on success. */
86
+ export interface ReadFileResponse {
87
+ readonly ok: boolean;
88
+ readonly content?: string;
89
+ readonly sizeBytes?: number;
90
+ readonly encoding?: string;
91
+ readonly error?: string;
92
+ }
93
+ /**
94
+ * Accumulates the streamed `/execute` NDJSON into a single
95
+ * {@link SandboxExecResult}, exactly as the docker/aci `execViaWorker`
96
+ * loops do: concatenate `stdout_delta` / `stderr_delta`, capture the
97
+ * terminal `result`, and **throw** on an `error` event.
98
+ *
99
+ * Transport-agnostic: feed it whole parsed {@link ExecEvent}s (the
100
+ * transport owns newline-framing → JSON.parse → here). Malformed lines
101
+ * are dropped by the transport's `JSON.parse` guard before they reach
102
+ * this accumulator, matching the docker loop's `SyntaxError` swallow.
103
+ */
104
+ export declare class ExecResultAccumulator {
105
+ private stdout;
106
+ private stderr;
107
+ private exitCode;
108
+ private timedOut;
109
+ private signal;
110
+ private settled;
111
+ private readonly start;
112
+ constructor(start?: number);
113
+ /**
114
+ * Apply one event. Returns `true` once a terminal `result` has been
115
+ * seen (so the transport can stop reading early if it wants).
116
+ * Throws if the event is an `error` — the same control flow the
117
+ * docker loop uses (`throw new Error(event.error)`).
118
+ */
119
+ push(event: ExecEvent): boolean;
120
+ get done(): boolean;
121
+ /** Build the SDK-shaped result. `durationMs` measured host-side. */
122
+ finish(): SandboxExecResult;
123
+ }
124
+ /**
125
+ * Parse a single NDJSON line into an {@link ExecEvent}, or `undefined`
126
+ * if the line is blank or not valid JSON (the docker loop's
127
+ * `SyntaxError` swallow). Non-`SyntaxError` problems are impossible
128
+ * here because we only `JSON.parse`; structural validation is by the
129
+ * `type` discriminator at the call site.
130
+ */
131
+ export declare function parseExecLine(line: string): ExecEvent | undefined;
132
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../src/backends/firecracker/protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The ONE wire contract, shared across both transports.
3
+ *
4
+ * The docker (`backends/docker/`) and ACI (`backends/aci-standby-pool/`)
5
+ * backends speak this contract over **HTTP**: a streaming NDJSON
6
+ * `/execute` response and base64-bodied `/read-file` / `/write-file`
7
+ * JSON requests, served by `worker/server.js`. The Firecracker
8
+ * backend speaks the **same NDJSON shapes and the same base64 file-IO
9
+ * shapes** — only the transport changes from HTTP-over-TCP to
10
+ * framed-over-vsock (see `transport.ts`).
11
+ *
12
+ * This module is the single source of truth for the message shapes
13
+ * and the streaming exec-line parser, so the two transports cannot
14
+ * drift. It deliberately carries NO transport concern (no `fetch`, no
15
+ * socket) — it is pure codec. The HTTP backends keep their own inline
16
+ * copies (they predate this module and stay UNTOUCHED per the P0
17
+ * scope); this module mirrors those shapes verbatim and is the
18
+ * canonical definition the vsock path consumes.
19
+ *
20
+ * Why mirror rather than refactor docker/aci onto it: the P0 scope is
21
+ * "do NOT touch docker or aci". Centralising the shapes here, with the
22
+ * inline docker copy pinned by `backends/docker/__tests__` and this
23
+ * module pinned by `backends/firecracker/__tests__`, keeps both honest
24
+ * without editing the frozen HTTP path. A later cleanup pass can fold
25
+ * docker/aci onto this module once it is no longer release-frozen.
26
+ */
27
+ // ---------------------------------------------------------------------------
28
+ // Streaming exec-line accumulator — the parser docker/aci inline today,
29
+ // lifted out so the vsock transport reuses it byte-for-byte.
30
+ // ---------------------------------------------------------------------------
31
+ /**
32
+ * Accumulates the streamed `/execute` NDJSON into a single
33
+ * {@link SandboxExecResult}, exactly as the docker/aci `execViaWorker`
34
+ * loops do: concatenate `stdout_delta` / `stderr_delta`, capture the
35
+ * terminal `result`, and **throw** on an `error` event.
36
+ *
37
+ * Transport-agnostic: feed it whole parsed {@link ExecEvent}s (the
38
+ * transport owns newline-framing → JSON.parse → here). Malformed lines
39
+ * are dropped by the transport's `JSON.parse` guard before they reach
40
+ * this accumulator, matching the docker loop's `SyntaxError` swallow.
41
+ */
42
+ export class ExecResultAccumulator {
43
+ stdout = '';
44
+ stderr = '';
45
+ exitCode = -1;
46
+ timedOut = false;
47
+ signal;
48
+ settled = false;
49
+ start;
50
+ constructor(start = Date.now()) {
51
+ this.start = start;
52
+ }
53
+ /**
54
+ * Apply one event. Returns `true` once a terminal `result` has been
55
+ * seen (so the transport can stop reading early if it wants).
56
+ * Throws if the event is an `error` — the same control flow the
57
+ * docker loop uses (`throw new Error(event.error)`).
58
+ */
59
+ push(event) {
60
+ if (event.type === 'stdout_delta') {
61
+ this.stdout += event.data;
62
+ return false;
63
+ }
64
+ if (event.type === 'stderr_delta') {
65
+ this.stderr += event.data;
66
+ return false;
67
+ }
68
+ if (event.type === 'result') {
69
+ this.exitCode = event.exitCode;
70
+ this.timedOut = event.timedOut;
71
+ this.settled = true;
72
+ return true;
73
+ }
74
+ // event.type === 'error'
75
+ throw new Error(event.error);
76
+ }
77
+ get done() {
78
+ return this.settled;
79
+ }
80
+ /** Build the SDK-shaped result. `durationMs` measured host-side. */
81
+ finish() {
82
+ return {
83
+ exitCode: this.exitCode,
84
+ stdout: this.stdout,
85
+ stderr: this.stderr,
86
+ ...(this.signal ? { signal: this.signal } : {}),
87
+ timedOut: this.timedOut,
88
+ durationMs: Date.now() - this.start,
89
+ };
90
+ }
91
+ }
92
+ /**
93
+ * Parse a single NDJSON line into an {@link ExecEvent}, or `undefined`
94
+ * if the line is blank or not valid JSON (the docker loop's
95
+ * `SyntaxError` swallow). Non-`SyntaxError` problems are impossible
96
+ * here because we only `JSON.parse`; structural validation is by the
97
+ * `type` discriminator at the call site.
98
+ */
99
+ export function parseExecLine(line) {
100
+ const trimmed = line.trim();
101
+ if (!trimmed)
102
+ return undefined;
103
+ try {
104
+ return JSON.parse(trimmed);
105
+ }
106
+ catch (err) {
107
+ if (err instanceof SyntaxError)
108
+ return undefined;
109
+ throw err;
110
+ }
111
+ }
112
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../src/backends/firecracker/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AA8EH,8EAA8E;AAC9E,wEAAwE;AACxE,6DAA6D;AAC7D,8EAA8E;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,OAAO,qBAAqB;IACzB,MAAM,GAAG,EAAE,CAAA;IACX,MAAM,GAAG,EAAE,CAAA;IACX,QAAQ,GAAG,CAAC,CAAC,CAAA;IACb,QAAQ,GAAG,KAAK,CAAA;IAChB,MAAM,CAAoB;IAC1B,OAAO,GAAG,KAAK,CAAA;IACN,KAAK,CAAQ;IAE9B,YAAY,QAAgB,IAAI,CAAC,GAAG,EAAE;QACrC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACnB,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,KAAgB;QACpB,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YACnC,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAA;YACzB,OAAO,KAAK,CAAA;QACb,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YACnC,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAA;YACzB,OAAO,KAAK,CAAA;QACb,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;YAC9B,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;YAC9B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;YACnB,OAAO,IAAI,CAAA;QACZ,CAAC;QACD,yBAAyB;QACzB,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAC7B,CAAC;IAED,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,OAAO,CAAA;IACpB,CAAC;IAED,oEAAoE;IACpE,MAAM;QACL,OAAO;YACN,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK;SACnC,CAAA;IACF,CAAC;CACD;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IAC3B,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAA;IAC9B,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAc,CAAA;IACxC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,GAAG,YAAY,WAAW;YAAE,OAAO,SAAS,CAAA;QAChD,MAAM,GAAG,CAAA;IACV,CAAC;AACF,CAAC"}