@claxedo/sandbox-manager 0.10.1 → 0.10.3

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 (42) hide show
  1. package/README.md +100 -30
  2. package/dist/checkpoint-manager.mjs +19 -0
  3. package/dist/contract.d.ts +40 -17
  4. package/dist/driver-billing.d.ts +23 -0
  5. package/dist/driver-catalog.mjs +57 -3
  6. package/dist/driver-ensure-input.d.ts +22 -0
  7. package/dist/drivers/agent37-client.d.ts +87 -0
  8. package/dist/drivers/agent37.d.ts +25 -0
  9. package/dist/drivers/agent37.mjs +888 -0
  10. package/dist/drivers/boat-client.d.ts +4 -4
  11. package/dist/drivers/boat-machine.d.ts +80 -0
  12. package/dist/drivers/boat-preparation.test-support.d.ts +2 -0
  13. package/dist/drivers/boat-template.mjs +196 -92
  14. package/dist/drivers/boat.d.ts +0 -17
  15. package/dist/drivers/boat.mjs +200 -108
  16. package/dist/drivers/cloudflare.d.ts +5 -9
  17. package/dist/drivers/cloudflare.mjs +71 -7
  18. package/dist/drivers/docker.mjs +57 -2
  19. package/dist/drivers/local-brokering.d.ts +12 -0
  20. package/dist/drivers/local-brokering.mjs +99 -24
  21. package/dist/drivers/modal.mjs +57 -2
  22. package/dist/drivers/transient-error.d.ts +0 -1
  23. package/dist/drivers/vercel.mjs +61 -3
  24. package/dist/egress-policy.d.ts +33 -32
  25. package/dist/egress-warnings.d.ts +4 -6
  26. package/dist/hosted-network-policy.d.ts +10 -64
  27. package/dist/hosted-network-policy.mjs +1 -1
  28. package/dist/image-name.d.ts +0 -1
  29. package/dist/image-name.mjs +5 -0
  30. package/dist/image.d.ts +1 -0
  31. package/dist/image.mjs +6 -0
  32. package/dist/index.mjs +180 -112
  33. package/dist/json.d.ts +1 -10
  34. package/dist/lease-failure.d.ts +20 -1
  35. package/dist/lease-types.d.ts +1 -10
  36. package/dist/runtime-env.d.ts +6 -0
  37. package/dist/runtime-env.mjs +5 -0
  38. package/dist/runtime-version.d.ts +25 -7
  39. package/dist/runtime-version.mjs +13 -2
  40. package/dist/start-admission.d.ts +32 -0
  41. package/dist/start-telemetry.d.ts +5 -3
  42. package/package.json +12 -4
@@ -31,6 +31,7 @@ var sandboxDriverCredentialFields = {
31
31
  { key: "worker_url", label: "Worker URL" }
32
32
  ],
33
33
  boat: [{ key: "api_key", label: "API Key", secret: true }],
34
+ agent37: [{ key: "api_key", label: "API Key", secret: true }],
34
35
  docker: [{ key: "image", label: "Image" }]
35
36
  };
36
37
  var sandboxDriverLabels = {
@@ -38,6 +39,7 @@ var sandboxDriverLabels = {
38
39
  vercel: "Vercel",
39
40
  cloudflare: "Cloudflare",
40
41
  boat: "Boat",
42
+ agent37: "Agent37",
41
43
  docker: "Docker"
42
44
  };
43
45
 
@@ -49,6 +51,13 @@ var SandboxRuntimeBootError = class extends Error {
49
51
  this.name = "SandboxRuntimeBootError";
50
52
  }
51
53
  };
54
+ var SandboxImageDownloadError = class extends Error {
55
+ imageDownloadFailed = true;
56
+ constructor(reason) {
57
+ super(reason);
58
+ this.name = "SandboxImageDownloadError";
59
+ }
60
+ };
52
61
  var SandboxHostGoneError = class extends Error {
53
62
  hostGone = true;
54
63
  constructor(reason) {
@@ -104,6 +113,10 @@ var WORKSPACE_RUNTIME_PUBLIC_ENV_KEYS = /* @__PURE__ */ new Set([
104
113
  function workspaceRuntimeBootSecrets(env) {
105
114
  return Object.entries(env).flatMap(([key, value]) => WORKSPACE_RUNTIME_PUBLIC_ENV_KEYS.has(key) ? [] : [value]);
106
115
  }
116
+ var REDACTED_VALUE_MIN_LENGTH = 8;
117
+ function redactWorkspaceRuntimeBootSecrets(text, secrets) {
118
+ return secrets.filter((secret) => secret.length >= REDACTED_VALUE_MIN_LENGTH).reduce((output, secret) => output.split(secret).join("[redacted]"), text);
119
+ }
107
120
  function workspaceRuntimeBootEnv(input) {
108
121
  assertWorkspaceRuntimeIdentityEnv(input.env);
109
122
  const env = {
@@ -149,6 +162,17 @@ function envFile(input) {
149
162
  }).join("\n") + "\n";
150
163
  }
151
164
 
165
+ // src/runtime-version.ts
166
+ var SERVED_WORKSPACE_RUNTIME_FLOOR = {
167
+ version: "0.10.1",
168
+ image: "ghcr.io/kyashrathore/claxedo-sandbox:workspace-runtime-0-10-1-40706b7356-agent-plugins-v8"
169
+ };
170
+ var MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION = SERVED_WORKSPACE_RUNTIME_FLOOR.version;
171
+ var SANDBOX_IMAGE_RUNTIME_VERSION_FILE = "opt/workspace-runtime/workspace-runtime-version";
172
+ function servedRuntimeVersionTest(file) {
173
+ return `version=$(cat ${file} 2>/dev/null) && [ -n "$version" ] && [ "$(printf '%s\\n%s\\n' ${MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION} "$version" | sort -V | head -n 1)" = ${MINIMUM_SERVED_WORKSPACE_RUNTIME_VERSION} ]`;
174
+ }
175
+
152
176
  // src/image-name.ts
153
177
  function assertSandboxImageReference(image) {
154
178
  if (!image || image !== image.trim() || image.startsWith("-") || /\s/.test(image)) {
@@ -168,6 +192,27 @@ var boatBilling = {
168
192
  large: { size: "large", vcpu: 8, memoryGb: 16, usdPerHour: 0.072 }
169
193
  }
170
194
  };
195
+ var AGENT37_USD_PER_MONTH = { vcpu: 0.8, memoryGb: 0.7, diskGb: 0.09 };
196
+ var AGENT37_MONTH_HOURS = 730;
197
+ var agent37MachineShapes = {
198
+ small: { cpu: 2, memory: 4, disk: 10 },
199
+ default: { cpu: 4, memory: 8, disk: 20 },
200
+ large: { cpu: 4, memory: 16, disk: 40 }
201
+ };
202
+ function agent37Rate(shape) {
203
+ const usdPerMonth = shape.cpu * AGENT37_USD_PER_MONTH.vcpu + shape.memory * AGENT37_USD_PER_MONTH.memoryGb + shape.disk * AGENT37_USD_PER_MONTH.diskGb;
204
+ return { size: `${shape.cpu} vCPU / ${shape.memory} GB`, vcpu: shape.cpu, memoryGb: shape.memory, usdPerHour: usdPerMonth / AGENT37_MONTH_HOURS };
205
+ }
206
+ var agent37Billing = {
207
+ source: { url: "https://www.agent37.com/docs/agents-api/billing", readOn: "2026-11-11" },
208
+ meter: "Agent37 meters an instance per minute at a monthly rate over 730 hours: $0.80 per vCPU, $0.70 per GB of memory and $0.09 per GB of disk while it runs, and the disk alone while it is stopped.",
209
+ rates: {
210
+ small: agent37Rate(agent37MachineShapes.small),
211
+ default: agent37Rate(agent37MachineShapes.default),
212
+ large: agent37Rate(agent37MachineShapes.large)
213
+ },
214
+ excludes: `the disk of a stopped instance, billed at $${AGENT37_USD_PER_MONTH.diskGb} per GB a month until the instance is deleted`
215
+ };
171
216
  var CLOUDFLARE_USD_PER_GIB_SECOND_MEMORY = 25e-7;
172
217
  var CLOUDFLARE_USD_PER_GB_SECOND_DISK = 7e-8;
173
218
  var CLOUDFLARE_USD_PER_VCPU_SECOND_ACTIVE = 2e-5;
@@ -253,8 +298,8 @@ var sandboxDriverCatalog = {
253
298
  hostWatchesIdle: true,
254
299
  targetAccess: "relay",
255
300
  secretBrokering: "native",
256
- // Credential handlers do not restrict unrelated destinations.
257
- egressControl: "none",
301
+ // The sandbox Worker starts the container without internet and routes each allowed host through itself; a Worker that cannot confirm that refuses the start.
302
+ egressControl: "hosts",
258
303
  // A container snapshot holds the whole root filesystem; the platform keeps it 30 days past its last restore and offers no deletion.
259
304
  persistence: {
260
305
  resume: "replacement-restore",
@@ -292,6 +337,33 @@ var sandboxDriverCatalog = {
292
337
  machineClasses: ["small", "default", "large"]
293
338
  }
294
339
  },
340
+ agent37: {
341
+ id: "agent37",
342
+ label: sandboxDriverLabels.agent37,
343
+ credentialFields: sandboxDriverCredentialFields.agent37,
344
+ runtimeNetwork: { controlPlane: "direct", relay: "configured" },
345
+ runtimeEnv: "callback",
346
+ preparedImage: true,
347
+ billing: agent37Billing,
348
+ metadata: {
349
+ driverRunsIn: ["worker", "node"],
350
+ hostStopBehavior: "suspends-host",
351
+ hostResumeBehavior: "same-host",
352
+ targetAccess: "relay",
353
+ secretBrokering: "none",
354
+ // An instance's network policy lists domains, reached over HTTPS on 443 alone, and IPv4 ranges; private ranges stay blocked whatever it lists.
355
+ egressControl: "hosts-and-cidrs",
356
+ persistence: {
357
+ resume: "same-sandbox",
358
+ capture: "none",
359
+ clone: false,
360
+ captureSource: "not-applicable",
361
+ retention: "provider-managed",
362
+ restoreMount: "same-resource"
363
+ },
364
+ machineClasses: ["small", "default", "large"]
365
+ }
366
+ },
295
367
  docker: {
296
368
  id: "docker",
297
369
  label: sandboxDriverLabels.docker,
@@ -364,10 +436,10 @@ var BoatApiError = class extends Error {
364
436
  }
365
437
  /** Boat answers 401 or 403 to the API key itself, never to a sandbox it was asked about. */
366
438
  providerKeyRefused;
367
- /** Create, fork and resume each count as one start (Boat's own refusal, 2026-10-15). */
439
+ /** Create, fork and resume each count as one start (Boat's own refusal). */
368
440
  providerRateLimited;
369
441
  startLimit;
370
- /** The state Boat's refusal gives the sandbox it refused, e.g. `archived` on a 400 `stop_failed` (2026-10-17). */
442
+ /** The state Boat's refusal gives the sandbox it refused, e.g. `archived` on a 400 `stop_failed`. */
371
443
  sandboxState;
372
444
  };
373
445
  function startCount(value) {
@@ -495,7 +567,7 @@ function createBoatClient(options) {
495
567
  },
496
568
  /**
497
569
  * `ttlSeconds` sets the auto-stop deadline to now plus that many seconds; `null` removes it.
498
- * Boat's reference example answers `sandbox.info`; the API answers `sandbox.updated` (2026-10-13).
570
+ * Boat's reference example answers `sandbox.info`; the API answers `sandbox.updated`.
499
571
  */
500
572
  async update(id, input) {
501
573
  const value = sandbox(await api(path(id), ["sandbox.updated", "sandbox.info"], { method: "PATCH", body: input }));
@@ -560,6 +632,97 @@ function isTransientDriverError(err, markers) {
560
632
  }
561
633
  var TRANSIENT_DRIVER_RETRY_MS = 2e3;
562
634
 
635
+ // src/drivers/boat-machine.ts
636
+ var IMAGE_PREPARATION_TIMEOUT_SECONDS = 600;
637
+ var CONTAINER_START_TIMEOUT_SECONDS = 120;
638
+ var DEFAULT_CONTAINER_NAME = "claxedo-runtime";
639
+ var DOCKER_DAEMON_WAIT_SECONDS = 120;
640
+ var RUNTIME_ENV_PATH = ".claxedo-runtime-env";
641
+ var REGISTRY_PASSWORD_PATH = ".claxedo-registry-password";
642
+ var BOOT_LOCK_PATH = "/run/lock/claxedo-boot.lock";
643
+ var BOOT_LOCK_HEADROOM_SECONDS = 60;
644
+ var CONTAINER_ENV_PATH = "/run/claxedo-runtime.env";
645
+ var PERSISTENT_ROOT = "claxedo-persistent";
646
+ var PERSISTENT_HOME_MOUNTS = [["claxedo", "/root/.claxedo"], ["workspace-runtime", "/root/.workspace-runtime"]];
647
+ var IMAGE_TREES = ["root", "usr", "etc", "var", "opt", "home", "srv"];
648
+ var IMAGE_TREE_SEED_EXCLUDES = ["root/.cache", "root/.npm/_cacache"];
649
+ var IMAGE_TREES_COMPLETE_MARKER = ".complete";
650
+ var SEED_CONTAINER_NAME = "claxedo-seed";
651
+ var STUB_IMAGE = "claxedo-runtime-stub";
652
+ var STUB_IMAGE_ARCHIVE = "stub.tar";
653
+ var IMAGE_LABEL = "claxedo.image";
654
+ var IMAGE_ENV_FILE = "image.env";
655
+ var ROOT_LINKS = ["bin", "lib", "lib64", "sbin"];
656
+ var ROOT_DIRECTORIES = ["boot", "dev", "media", "mnt", "proc", "run", "sys", "tmp", ...IMAGE_TREES];
657
+ var BOAT_SAVED_IMAGES = "/var/lib/ascii-docker-images";
658
+ var DOCKER_NAME_IN_USE = "is already in use by container";
659
+ var CONTAINER_CREATE_ATTEMPTS = 5;
660
+ var CONTAINER_SECURITY_OPTIONS = "--security-opt seccomp=unconfined --security-opt apparmor=unconfined";
661
+ var VM_DOCKER_INIT = "/usr/libexec/docker/docker-init";
662
+ var CONTAINER_INIT_PATH = "/claxedo-init";
663
+ function holdingBootLock(commandTimeoutSeconds, script) {
664
+ const wait = commandTimeoutSeconds - BOOT_LOCK_HEADROOM_SECONDS;
665
+ return `exec 9>>${BOOT_LOCK_PATH} && { flock -w ${wait} 9 || { echo "another boot of this machine held ${BOOT_LOCK_PATH} for ${wait} s" >&2; exit 1; }; } && ${script}`;
666
+ }
667
+ function containerHostname(workspaceId) {
668
+ return workspaceId.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "").slice(0, 63) || "claxedo";
669
+ }
670
+ function bootedImageFile(staged) {
671
+ return `${staged.env}.image`;
672
+ }
673
+ function bootedImage(prepareStdout) {
674
+ return prepareStdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).pop();
675
+ }
676
+ var stubLabel = `docker image inspect --format '{{index .Config.Labels "${IMAGE_LABEL}"}}' ${STUB_IMAGE} 2>/dev/null`;
677
+ var treesFunctions = [
678
+ `trees_of() { printf '%s/${PERSISTENT_ROOT}/image-%s' "$(pwd)" "$(printf %s "$1" | sed 's/[^A-Za-z0-9._-]/-/g')"; }`,
679
+ `seeded() { [ -f "$1/${IMAGE_TREES_COMPLETE_MARKER}" ] && [ -f "$1/${STUB_IMAGE_ARCHIVE}" ] && [ -f "$1/${IMAGE_ENV_FILE}" ]; }`,
680
+ `served() { ${servedRuntimeVersionTest(`"$1/${SANDBOX_IMAGE_RUNTIME_VERSION_FILE}"`)}; }`
681
+ ].join(" && ");
682
+ var IMAGE_DOWNLOAD_INTERRUPTED = "image_download_interrupted";
683
+ var KEPT_IMAGE_SKIPPED = "kept_image_skipped:";
684
+ var PULL_RESUMING = `pull_resuming() { finished=$(mktemp) && while :; do if output=$(docker pull "$1" 2>&1); then rm -f "$finished"; return 0; fi; case "$output" in *"connection reset by peer"*|*"unexpected EOF"*|*"i/o timeout"*|*"TLS handshake timeout"*) ;; *) printf '%s\\n' "$output" >&2; rm -f "$finished"; return 1 ;; esac; before=$(wc -l < "$finished"); { cat "$finished"; printf '%s\\n' "$output" | sed -n -e 's/^\\([0-9a-f]\\{12\\}\\): Download complete$/\\1/p' -e 's/^\\([0-9a-f]\\{12\\}\\): Pull complete$/\\1/p'; } | sort -u > "$finished.next" && mv "$finished.next" "$finished"; [ "$(wc -l < "$finished")" -gt "$before" ] || { printf '%s\\n' "$output" ${IMAGE_DOWNLOAD_INTERRUPTED} >&2; rm -f "$finished"; return 1; }; done; }`;
685
+ function machineBootScripts(boot) {
686
+ const named = shell(boot.image);
687
+ const trees = `"$(trees_of ${named})"`;
688
+ const bootedFile = bootedImageFile(boot.staged);
689
+ const containerOfBooted = `[ "$(docker container inspect --format '{{index .Config.Labels "${IMAGE_LABEL}"}}' ${boot.containerName} 2>/dev/null)" = "$booted" ]`;
690
+ const bootScript = [
691
+ `. ${CONTAINER_ENV_PATH}`,
692
+ `mkdir -p ${shell(boot.directory)}`,
693
+ `cd ${shell(boot.directory)}`,
694
+ `exec ${boot.runtimeCommand}`
695
+ ].join(" && ");
696
+ const mounts = [["workspace", boot.directory], ...PERSISTENT_HOME_MOUNTS];
697
+ const run = `docker run -d --name ${boot.containerName} --hostname ${shell(containerHostname(boot.workspaceId))} -p ${boot.port}:${boot.port} -v ${VM_DOCKER_INIT}:${CONTAINER_INIT_PATH}:ro -v "$(pwd)/${RUNTIME_ENV_PATH}:${CONTAINER_ENV_PATH}:ro" --env-file "$trees/${IMAGE_ENV_FILE}" ` + IMAGE_TREES.map((tree) => `-v "$trees/${tree}":/${tree} `).join("") + mounts.map(([source, target]) => `-v "$(pwd)/${PERSISTENT_ROOT}/${source}":${shell(target)} `).join("") + `${CONTAINER_SECURITY_OPTIONS} --entrypoint ${CONTAINER_INIT_PATH} ${STUB_IMAGE} -- sh -lc ${shell(bootScript)}`;
698
+ const seedTrees = `sudo rm -rf ${trees} && sudo mkdir -p ${trees} && { docker rm -f ${SEED_CONTAINER_NAME} >/dev/null 2>&1 || true; } && docker create --name ${SEED_CONTAINER_NAME} ${named} true >/dev/null && for tree in ${IMAGE_TREES.join(" ")}; do docker cp ${SEED_CONTAINER_NAME}:/$tree - | sudo tar -xf - -C ${trees} ${IMAGE_TREE_SEED_EXCLUDES.map((exclude) => `--exclude=${exclude}`).join(" ")} && [ -d ${trees}/$tree ] || exit 1; done && docker rm ${SEED_CONTAINER_NAME} >/dev/null && sudo touch ${trees}/${IMAGE_TREES_COMPLETE_MARKER}`;
699
+ const seedStub = `stub=$(mktemp -d) && docker image inspect --format '{{range .Config.Env}}{{println .}}{{end}}' ${named} > "$stub/${IMAGE_ENV_FILE}" && workdir=$(docker image inspect --format '{{or .Config.WorkingDir "/"}}' ${named}) && mkdir "$stub/root" ${ROOT_DIRECTORIES.map((directory) => `"$stub/root/${directory}"`).join(" ")} && ${ROOT_LINKS.map((link) => `ln -s usr/${link} "$stub/root/${link}"`).join(" && ")} && chmod 1777 "$stub/root/tmp" && tar --owner=0 --group=0 --numeric-owner -cf "$stub/skeleton.tar" -C "$stub/root" . && docker import --change "WORKDIR $workdir" --change ${shell(`LABEL ${IMAGE_LABEL}=${boot.image}`)} "$stub/skeleton.tar" ${STUB_IMAGE} >/dev/null && docker save -o "$stub/${STUB_IMAGE_ARCHIVE}" ${STUB_IMAGE} && sudo mv "$stub/${IMAGE_ENV_FILE}" "$stub/${STUB_IMAGE_ARCHIVE}" ${trees}/ && rm -rf "$stub"`;
700
+ const seedNamed = `{ docker image inspect ${named} >/dev/null 2>&1 || pull_resuming ${named}; } && { [ -f ${trees}/${IMAGE_TREES_COMPLETE_MARKER} ] || { ${seedTrees}; }; } && ${seedStub}`;
701
+ const keepPrevious = `previous=$(${stubLabel}) && [ -n "$previous" ] && seeded "$(trees_of "$previous")" && served "$(trees_of "$previous")"`;
702
+ const keptImageSkipped = `echo "${KEPT_IMAGE_SKIPPED} stub=\${previous:-none} seeded=$(seeded "$(trees_of "$previous")" && echo yes || echo no) version=$(cat "$(trees_of "$previous")/${SANDBOX_IMAGE_RUNTIME_VERSION_FILE}" 2>/dev/null || echo none)" >&2`;
703
+ const removeImage = `if docker image inspect "$booted" >/dev/null 2>&1; then leftovers=$(docker ps -aq --filter ancestor="$booted") && { [ -z "$leftovers" ] || docker rm -f $leftovers >/dev/null; } && id=$(docker image inspect --format '{{.Id}}' "$booted") && docker rmi "$booted" >/dev/null && sudo rm -f ${BOAT_SAVED_IMAGES}/"\${id#sha256:}".tar; fi`;
704
+ const prepare = [
705
+ `chmod 600 ${boot.staged.env}`,
706
+ `find . -maxdepth 1 \\( -name '${RUNTIME_ENV_PATH}.*' -o -name '${REGISTRY_PASSWORD_PATH}.*' \\) -mmin +${boot.staleStagedMinutes} -exec rm -f {} +`,
707
+ `timeout ${DOCKER_DAEMON_WAIT_SECONDS} sh -c 'until docker info >/dev/null 2>&1; do sleep 1; done'`,
708
+ `[ -x ${VM_DOCKER_INIT} ]`,
709
+ ...boot.registry && boot.staged.password ? [`{ chmod 600 ${boot.staged.password}; docker login ${shell(boot.registry.server)} --username ${shell(boot.registry.username)} --password-stdin < ${boot.staged.password}; rc=$?; rm -f ${boot.staged.password}; (exit $rc); }`] : [],
710
+ treesFunctions,
711
+ PULL_RESUMING,
712
+ `booted=${named}`,
713
+ `if ! seeded ${trees}; then ` + (boot.keepPreviousImage ? `if ${keepPrevious}; then booted=$previous; else ${keptImageSkipped}; ${seedNamed}; fi` : seedNamed) + `; fi`,
714
+ `{ [ "$(${stubLabel})" = "$booted" ] || docker load -i "$(trees_of "$booted")/${STUB_IMAGE_ARCHIVE}" >/dev/null; }`,
715
+ `sudo find "$(pwd)/${PERSISTENT_ROOT}" -mindepth 1 -maxdepth 1 -name 'image-*' ! -path "$(trees_of "$booted")" -exec rm -rf {} +`,
716
+ `printf '%s\\n' "$booted" > ${bootedFile}`,
717
+ `printf '%s\\n' "$booted"`
718
+ ];
719
+ const start = `${treesFunctions} && booted=$(cat ${bootedFile}) && trees=$(trees_of "$booted") && rm -f ${bootedFile} && mv -f ${boot.staged.env} ${RUNTIME_ENV_PATH} && up=; for attempt in $(seq ${CONTAINER_CREATE_ATTEMPTS}); do [ "$attempt" = 1 ] || sleep 1; if ${containerOfBooted}; then failure=$(docker start ${boot.containerName} 2>&1 >/dev/null) && { up=1; break; }; else docker rm -f ${boot.containerName} >/dev/null 2>&1; failure=$(${run} 2>&1 >/dev/null) && { up=1; break; }; fi; case "$failure" in *${shell(DOCKER_NAME_IN_USE)}*) ;; *) break ;; esac; done; [ -n "$up" ] || { echo "$failure" >&2; false; } && ${removeImage}`;
720
+ return {
721
+ prepare: holdingBootLock(IMAGE_PREPARATION_TIMEOUT_SECONDS, prepare.join(" && ")),
722
+ start: holdingBootLock(CONTAINER_START_TIMEOUT_SECONDS, start)
723
+ };
724
+ }
725
+
563
726
  // src/drivers/boat.ts
564
727
  function formatBoatRuntimeTemplate(template) {
565
728
  return JSON.stringify({ image: template.image, shareCode: template.shareCode });
@@ -579,7 +742,6 @@ function parseBoatRuntimeTemplate(setting) {
579
742
  }
580
743
  var DEFAULT_RUNTIME_COMMAND = "/usr/local/bin/workspace-runtime";
581
744
  var DEFAULT_WORKSPACE_DIR = "/workspace";
582
- var DEFAULT_CONTAINER_NAME = "claxedo-runtime";
583
745
  var DEFAULT_PROVISION_TIMEOUT_MS = 12e4;
584
746
  var DEFAULT_STATE_POLL_MS = { firstMs: 250, maxMs: 2e3 };
585
747
  var DEFAULT_HEALTH_TIMEOUT_MS = 10 * 6e4;
@@ -587,39 +749,11 @@ var DEFAULT_HEALTH_INTERVAL_MS = 100;
587
749
  var HEALTH_PROBE_WINDOW_SECONDS = 50;
588
750
  var HEALTH_PROBE_MARGIN_SECONDS = 10;
589
751
  var BOOTING_CONTAINER_STATES = /* @__PURE__ */ new Set(["running", "paused"]);
590
- var IMAGE_PREPARATION_TIMEOUT_SECONDS = 600;
591
- var CONTAINER_START_TIMEOUT_SECONDS = 120;
592
752
  var HOST_PUBLISH_TIMEOUT_SECONDS = 120;
593
753
  var BOOT_TTL_SECONDS = 30 * 60;
594
- var DOCKER_DAEMON_WAIT_SECONDS = 120;
595
- var RUNTIME_ENV_PATH = ".claxedo-runtime-env";
596
- var REGISTRY_PASSWORD_PATH = ".claxedo-registry-password";
597
- var BOOT_LOCK_PATH = "/run/lock/claxedo-boot.lock";
598
- var BOOT_LOCK_HEADROOM_SECONDS = 60;
599
- var CONTAINER_ENV_PATH = "/run/claxedo-runtime.env";
600
- var PERSISTENT_ROOT = "claxedo-persistent";
601
- var PERSISTENT_HOME_MOUNTS = [["claxedo", "/root/.claxedo"], ["workspace-runtime", "/root/.workspace-runtime"]];
602
- var IMAGE_TREES = ["root", "usr", "etc", "var", "opt", "home", "srv"];
603
- var IMAGE_TREE_SEED_EXCLUDES = ["root/.cache", "root/.npm/_cacache"];
604
- var IMAGE_TREES_COMPLETE_MARKER = ".complete";
605
- var SEED_CONTAINER_NAME = "claxedo-seed";
606
- var STUB_IMAGE = "claxedo-runtime-stub";
607
- var STUB_IMAGE_ARCHIVE = "stub.tar";
608
- var IMAGE_LABEL = "claxedo.image";
609
- var IMAGE_ENV_FILE = "image.env";
610
- var ROOT_LINKS = ["bin", "lib", "lib64", "sbin"];
611
- var ROOT_DIRECTORIES = ["boot", "dev", "media", "mnt", "proc", "run", "sys", "tmp", ...IMAGE_TREES];
612
- var BOAT_SAVED_IMAGES = "/var/lib/ascii-docker-images";
613
- var BOAT_CONTAINER_RECORD = "/etc/ascii-docker.json";
614
- var DOCKER_NAME_IN_USE = "is already in use by container";
615
- var CONTAINER_CREATE_ATTEMPTS = 5;
616
- var CONTAINER_SECURITY_OPTIONS = "--security-opt seccomp=unconfined --security-opt apparmor=unconfined";
617
- var VM_DOCKER_INIT = "/usr/libexec/docker/docker-init";
618
- var CONTAINER_INIT_PATH = "/claxedo-init";
619
754
  var CONTAINER_STOP_GRACE_SECONDS = 30;
620
755
  var HOST_RELAY_NOT_STOPPED = "The sandbox could not stop the localhost relay of this port. Try again.";
621
756
  var STDERR_TAIL_CHARS = 600;
622
- var REDACTED_VALUE_MIN_LENGTH = 8;
623
757
  var READY_STATES = /* @__PURE__ */ new Set(["ready", "idle", "running"]);
624
758
  var PENDING_STATES = /* @__PURE__ */ new Set(["init", "provisioning", "provisioned", "cloning"]);
625
759
  var TRANSIENT_MARKERS = ["transport_failed", "idempotency_in_progress"];
@@ -627,16 +761,6 @@ var GONE_CODES = /* @__PURE__ */ new Set(["not_found", "snapshot_data_expired"])
627
761
  var RESUME_UNDER_WAY = "sandbox_resuming";
628
762
  var NOT_RUNNING_CODES = /* @__PURE__ */ new Set(["machine_not_running", "sandbox_not_ready"]);
629
763
  var TEMPLATE_UNREADABLE_CODES = /* @__PURE__ */ new Set(["snapshot_not_found", "api_key_action_forbidden", "api_key_sandbox_forbidden"]);
630
- function boatTemplateResetCommand() {
631
- const bootFiles = [
632
- `${PERSISTENT_ROOT}/workspace`,
633
- ...PERSISTENT_HOME_MOUNTS.map(([source]) => `${PERSISTENT_ROOT}/${source}`),
634
- RUNTIME_ENV_PATH,
635
- `${RUNTIME_ENV_PATH}.*`,
636
- `${REGISTRY_PASSWORD_PATH}.*`
637
- ];
638
- return `{ docker rm -f ${DEFAULT_CONTAINER_NAME} >/dev/null 2>&1 || true; } && sudo rm -rf ${bootFiles.join(" ")} && [ -z "$(docker ps -aq)" ] && echo '[]' | sudo tee ${BOAT_CONTAINER_RECORD} >/dev/null && sudo find ${BOAT_SAVED_IMAGES} -mindepth 1 -delete && ls -d ${PERSISTENT_ROOT}/image-*/${IMAGE_TREES_COMPLETE_MARKER}`;
639
- }
640
764
  function hostGoneOrThrow(err) {
641
765
  if (err instanceof BoatApiError && GONE_CODES.has(err.code)) throw new SandboxHostGoneError(err.message);
642
766
  throw err;
@@ -649,24 +773,11 @@ function archivedOrThrow(err) {
649
773
  if (err instanceof BoatApiError && err.code === "stop_failed" && err.sandboxState === "archived") return;
650
774
  hostGoneOrThrow(err);
651
775
  }
652
- function holdingBootLock(commandTimeoutSeconds, script) {
653
- const wait = commandTimeoutSeconds - BOOT_LOCK_HEADROOM_SECONDS;
654
- return `exec 9>>${BOOT_LOCK_PATH} && { flock -w ${wait} 9 || { echo "another boot of this machine held ${BOOT_LOCK_PATH} for ${wait} s" >&2; exit 1; }; } && ${script}`;
655
- }
656
776
  function sleep(ms) {
657
777
  return new Promise((resolve) => setTimeout(resolve, ms));
658
778
  }
659
- function scrubbed(text, secrets) {
660
- return secrets.filter((secret) => secret.length >= REDACTED_VALUE_MIN_LENGTH).reduce((output, secret) => output.split(secret).join("[redacted]"), text);
661
- }
662
779
  function scrubbedTail(text, secrets) {
663
- return scrubbed(text, secrets).trim().slice(-STDERR_TAIL_CHARS);
664
- }
665
- function imageTreesDirectory(image) {
666
- return `image-${image.replace(/[^A-Za-z0-9._-]/g, "-")}`;
667
- }
668
- function containerHostname(workspaceId) {
669
- return workspaceId.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "").slice(0, 63) || "claxedo";
780
+ return redactWorkspaceRuntimeBootSecrets(text, secrets).trim().slice(-STDERR_TAIL_CHARS);
670
781
  }
671
782
  var BoatDriverError = class extends Error {
672
783
  constructor(message) {
@@ -748,43 +859,6 @@ function createBoatSandboxDriver(options) {
748
859
  wait = Math.min(wait * 2, statePollMs.maxMs);
749
860
  }
750
861
  }
751
- function containerScripts(input, staged) {
752
- const port = runtimePort(input);
753
- const directory = workspaceDirectory(input);
754
- const reference = resolveImage(input);
755
- const image = shell(reference);
756
- const trees = `"$(pwd)/${PERSISTENT_ROOT}/${imageTreesDirectory(reference)}"`;
757
- const labelledWithImage = (object, name) => `[ "$(docker ${object} inspect --format '{{index .Config.Labels "${IMAGE_LABEL}"}}' ${name} 2>/dev/null)" = ${image} ]`;
758
- const bootScript = [
759
- `. ${CONTAINER_ENV_PATH}`,
760
- `mkdir -p ${shell(directory)}`,
761
- `cd ${shell(directory)}`,
762
- `exec ${runtimeCommand}`
763
- ].join(" && ");
764
- const mounts = [["workspace", directory], ...PERSISTENT_HOME_MOUNTS];
765
- const run = `docker run -d --name ${containerName} --hostname ${shell(containerHostname(input.workspaceId))} -p ${port}:${port} -v ${VM_DOCKER_INIT}:${CONTAINER_INIT_PATH}:ro -v "$(pwd)/${RUNTIME_ENV_PATH}:${CONTAINER_ENV_PATH}:ro" --env-file ${trees}/${IMAGE_ENV_FILE} ` + IMAGE_TREES.map((tree) => `-v ${trees}/${tree}:/${tree} `).join("") + mounts.map(([source, target]) => `-v "$(pwd)/${PERSISTENT_ROOT}/${source}":${shell(target)} `).join("") + `${CONTAINER_SECURITY_OPTIONS} --entrypoint ${CONTAINER_INIT_PATH} ${STUB_IMAGE} -- sh -lc ${shell(bootScript)}`;
766
- const seedTrees = `sudo rm -rf ${trees} && sudo mkdir -p ${trees} && { docker rm -f ${SEED_CONTAINER_NAME} >/dev/null 2>&1 || true; } && docker create --name ${SEED_CONTAINER_NAME} ${image} true >/dev/null && for tree in ${IMAGE_TREES.join(" ")}; do docker cp ${SEED_CONTAINER_NAME}:/$tree - | sudo tar -xf - -C ${trees} ${IMAGE_TREE_SEED_EXCLUDES.map((exclude) => `--exclude=${exclude}`).join(" ")} && [ -d ${trees}/$tree ] || exit 1; done && docker rm ${SEED_CONTAINER_NAME} >/dev/null && sudo touch ${trees}/${IMAGE_TREES_COMPLETE_MARKER}`;
767
- const seedStub = `stub=$(mktemp -d) && docker image inspect --format '{{range .Config.Env}}{{println .}}{{end}}' ${image} > "$stub/${IMAGE_ENV_FILE}" && workdir=$(docker image inspect --format '{{or .Config.WorkingDir "/"}}' ${image}) && mkdir "$stub/root" ${ROOT_DIRECTORIES.map((directory2) => `"$stub/root/${directory2}"`).join(" ")} && ${ROOT_LINKS.map((link) => `ln -s usr/${link} "$stub/root/${link}"`).join(" && ")} && chmod 1777 "$stub/root/tmp" && tar --owner=0 --group=0 --numeric-owner -cf "$stub/skeleton.tar" -C "$stub/root" . && docker import --change "WORKDIR $workdir" --change ${shell(`LABEL ${IMAGE_LABEL}=${reference}`)} "$stub/skeleton.tar" ${STUB_IMAGE} >/dev/null && docker save -o "$stub/${STUB_IMAGE_ARCHIVE}" ${STUB_IMAGE} && sudo mv "$stub/${IMAGE_ENV_FILE}" "$stub/${STUB_IMAGE_ARCHIVE}" ${trees}/ && rm -rf "$stub"`;
768
- const removeImage = `if docker image inspect ${image} >/dev/null 2>&1; then leftovers=$(docker ps -aq --filter ancestor=${image}) && { [ -z "$leftovers" ] || docker rm -f $leftovers >/dev/null; } && id=$(docker image inspect --format '{{.Id}}' ${image}) && docker rmi ${image} >/dev/null && sudo rm -f ${BOAT_SAVED_IMAGES}/"\${id#sha256:}".tar; fi`;
769
- const seeded = [IMAGE_TREES_COMPLETE_MARKER, STUB_IMAGE_ARCHIVE, IMAGE_ENV_FILE].map((file) => `[ -f ${trees}/${file} ]`).join(" && ");
770
- const prepare = [
771
- `chmod 600 ${staged.env}`,
772
- `find . -maxdepth 1 \\( -name '${RUNTIME_ENV_PATH}.*' -o -name '${REGISTRY_PASSWORD_PATH}.*' \\) -mmin +${BOOT_TTL_SECONDS / 60} -exec rm -f {} +`,
773
- `timeout ${DOCKER_DAEMON_WAIT_SECONDS} sh -c 'until docker info >/dev/null 2>&1; do sleep 1; done'`,
774
- `[ -x ${VM_DOCKER_INIT} ]`,
775
- ...options.registryAuth && staged.password ? [`{ chmod 600 ${staged.password}; docker login ${shell(options.registryAuth.server)} --username ${shell(options.registryAuth.username)} --password-stdin < ${staged.password}; rc=$?; rm -f ${staged.password}; (exit $rc); }`] : [],
776
- // Trees an earlier start marked complete hold what a person or a harness
777
- // changed since, so a seed missing only its stub or env builds those alone.
778
- `if ! { ${seeded}; }; then { docker image inspect ${image} >/dev/null 2>&1 || docker pull ${image} >/dev/null; } && { [ -f ${trees}/${IMAGE_TREES_COMPLETE_MARKER} ] || { ${seedTrees}; }; } && ${seedStub}; fi`,
779
- `{ ${labelledWithImage("image", STUB_IMAGE)} || docker load -i ${trees}/${STUB_IMAGE_ARCHIVE} >/dev/null; }`,
780
- `sudo find "$(pwd)/${PERSISTENT_ROOT}" -mindepth 1 -maxdepth 1 -name 'image-*' ! -path ${trees} -exec rm -rf {} +`
781
- ];
782
- const start = `mv -f ${staged.env} ${RUNTIME_ENV_PATH} && up=; for attempt in $(seq ${CONTAINER_CREATE_ATTEMPTS}); do [ "$attempt" = 1 ] || sleep 1; if ${labelledWithImage("container", containerName)}; then failure=$(docker start ${containerName} 2>&1 >/dev/null) && { up=1; break; }; else docker rm -f ${containerName} >/dev/null 2>&1; failure=$(${run} 2>&1 >/dev/null) && { up=1; break; }; fi; case "$failure" in *${shell(DOCKER_NAME_IN_USE)}*) ;; *) break ;; esac; done; [ -n "$up" ] || { echo "$failure" >&2; false; } && ${removeImage}`;
783
- return {
784
- prepare: holdingBootLock(IMAGE_PREPARATION_TIMEOUT_SECONDS, prepare.join(" && ")),
785
- start: holdingBootLock(CONTAINER_START_TIMEOUT_SECONDS, start)
786
- };
787
- }
788
862
  function healthProbe(port, windowSeconds) {
789
863
  const booting = [...BOOTING_CONTAINER_STATES].join("|");
790
864
  return `end=$(($(date +%s) + ${windowSeconds})); while :; do code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:${port}/global/health); [ "$code" = 200 ] && break; state=$(docker inspect --format '{{.State.Status}}' ${shell(containerName)} 2>/dev/null || echo missing); case "$state" in ${booting}) ;; *) break ;; esac; [ "$(date +%s)" -lt "$end" ] || break; sleep ${healthIntervalMs / 1e3}; done; echo "$code"; echo "$state"`;
@@ -813,29 +887,48 @@ function createBoatSandboxDriver(options) {
813
887
  command: `docker inspect --format 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}}' ${shell(containerName)}; docker logs --tail 20 ${shell(containerName)} 2>&1`,
814
888
  timeoutSeconds: 10
815
889
  });
816
- diagnostic = runtimeBootDiagnostic(scrubbed(`${result.stdout}
890
+ diagnostic = runtimeBootDiagnostic(redactWorkspaceRuntimeBootSecrets(`${result.stdout}
817
891
  ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
818
892
  } catch {
819
893
  diagnostic = "container diagnostics unavailable";
820
894
  }
821
895
  throw new SandboxRuntimeBootError(`Boat ${sandboxId} runtime did not become healthy: ${last}; ${diagnostic}`);
822
896
  }
823
- async function startContainer(sandboxId, input, hostId) {
897
+ async function startContainer(sandboxId, input, hostId, keepPreviousImage) {
824
898
  const env = { ...bootEnv(input, hostId), ...await options.env?.(input, { id: hostId }) };
825
899
  const boot2 = crypto.randomUUID();
826
900
  const staged = {
827
901
  env: `${RUNTIME_ENV_PATH}.${boot2}`,
828
902
  ...options.registryAuth ? { password: `${REGISTRY_PASSWORD_PATH}.${boot2}` } : {}
829
903
  };
830
- const scripts = containerScripts(input, staged);
904
+ const image = resolveImage(input);
905
+ const scripts = machineBootScripts({
906
+ image,
907
+ keepPreviousImage: keepPreviousImage && image === options.image,
908
+ workspaceId: input.workspaceId,
909
+ directory: workspaceDirectory(input),
910
+ port: runtimePort(input),
911
+ runtimeCommand,
912
+ containerName,
913
+ ...options.registryAuth ? { registry: { server: options.registryAuth.server, username: options.registryAuth.username } } : {},
914
+ staged,
915
+ staleStagedMinutes: BOOT_TTL_SECONDS / 60
916
+ });
831
917
  await client.writeFile(sandboxId, { path: staged.env, content: envFile(env) });
832
918
  if (options.registryAuth && staged.password) {
833
919
  await client.writeFile(sandboxId, { path: staged.password, content: options.registryAuth.password });
834
920
  }
835
921
  const secrets = [...workspaceRuntimeBootSecrets(env), ...options.registryAuth ? [options.registryAuth.password] : []];
836
922
  let failure;
923
+ let booted;
837
924
  try {
838
- await execOrThrow(sandboxId, scripts.prepare, "runtime image preparation", { timeoutSeconds: IMAGE_PREPARATION_TIMEOUT_SECONDS, secrets });
925
+ const prepared = await execOrThrow(sandboxId, scripts.prepare, "runtime image preparation", { timeoutSeconds: IMAGE_PREPARATION_TIMEOUT_SECONDS, secrets }).catch((error) => {
926
+ throw error instanceof BoatDriverError && error.message.includes(IMAGE_DOWNLOAD_INTERRUPTED) ? new SandboxImageDownloadError(error.message) : error;
927
+ });
928
+ booted = bootedImage(prepared.stdout);
929
+ if (!booted) throw new BoatDriverError(`Boat ${sandboxId} runtime image preparation named no image it booted`);
930
+ const skipped = keepPreviousImage ? prepared.stderr.split("\n").find((line) => line.startsWith(KEPT_IMAGE_SKIPPED)) : void 0;
931
+ if (skipped) console.warn("A Boat wake took the deployment's image instead of the one the machine last booted", { sandboxId, workspaceId: input.workspaceId, found: skipped.slice(KEPT_IMAGE_SKIPPED.length).trim() });
839
932
  try {
840
933
  await execOrThrow(sandboxId, scripts.start, "container start", { timeoutSeconds: CONTAINER_START_TIMEOUT_SECONDS, secrets });
841
934
  } catch (error) {
@@ -846,19 +939,19 @@ ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
846
939
  }
847
940
  try {
848
941
  if (options.registryAuth || failure !== void 0) {
849
- await execOrThrow(sandboxId, `rm -f ${[staged.env, staged.password].filter(Boolean).join(" ")}`, "staged file cleanup", { secrets });
942
+ await execOrThrow(sandboxId, `rm -f ${[staged.env, staged.password, bootedImageFile(staged)].filter(Boolean).join(" ")}`, "staged file cleanup", { secrets });
850
943
  }
851
944
  } catch (cleanupFailure) {
852
945
  if (failure === void 0) throw cleanupFailure;
853
946
  if (failure instanceof Error) failure.message = `${failure.message}; ${cleanupFailure instanceof Error ? cleanupFailure.message : String(cleanupFailure)}`;
854
947
  }
855
- if (failure !== void 0) throw failure;
856
- return secrets;
948
+ if (failure !== void 0 || !booted) throw failure;
949
+ return { secrets, booted };
857
950
  }
858
- async function boot(sandboxId, input, hostId) {
951
+ async function boot(sandboxId, input, hostId, keepPreviousImage) {
859
952
  await input.onMachineReady?.();
860
953
  const port = runtimePort(input);
861
- const secrets = await startContainer(sandboxId, input, hostId);
954
+ const { secrets, booted } = await startContainer(sandboxId, input, hostId, keepPreviousImage);
862
955
  await input.onImageReady?.();
863
956
  await execOrThrow(sandboxId, holdingBootLock(HOST_PUBLISH_TIMEOUT_SECONDS, `host ${port} --public`), "host publish", {
864
957
  timeoutSeconds: HOST_PUBLISH_TIMEOUT_SECONDS,
@@ -878,7 +971,7 @@ ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
878
971
  hostId,
879
972
  driverResourceId: sandboxId,
880
973
  driver: { id: "boat", resourceId: sandboxId },
881
- labels: { ...input.labels, ...resolveImage(input) === options.image ? { [SANDBOX_IMAGE_LABEL]: options.image } : {} }
974
+ labels: { ...input.labels, ...resolveImage(input) === options.image ? { [SANDBOX_IMAGE_LABEL]: booted } : {} }
882
975
  };
883
976
  }
884
977
  function assertNetwork(input) {
@@ -927,7 +1020,7 @@ ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
927
1020
  if (failures.length > 1) throw new AggregateError(failures, `Boat ${created.id} resource handoff and deletion failed`);
928
1021
  const ready = await waitUntilReady(created.id);
929
1022
  if (!ready) return { provisioning: true, retryAfterMs: 0 };
930
- return boot(created.id, input, hostId);
1023
+ return boot(created.id, input, hostId, false);
931
1024
  }
932
1025
  async function resumeHost(input) {
933
1026
  const sandboxId = input.lease.sandboxId;
@@ -947,7 +1040,7 @@ ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
947
1040
  }
948
1041
  const ready = await waitUntilReady(sandboxId);
949
1042
  if (!ready) return { provisioning: true, retryAfterMs: 0 };
950
- return boot(sandboxId, input.ensure, hostId);
1043
+ return boot(sandboxId, input.ensure, hostId, !input.ensure.updateImage);
951
1044
  }
952
1045
  async function stop(target) {
953
1046
  await client.command(target.sandboxId, {
@@ -983,7 +1076,6 @@ ${result.stderr}`, secrets), STDERR_TAIL_CHARS);
983
1076
  };
984
1077
  }
985
1078
  export {
986
- boatTemplateResetCommand,
987
1079
  createBoatSandboxDriver,
988
1080
  formatBoatRuntimeTemplate,
989
1081
  parseBoatRuntimeTemplate
@@ -5,19 +5,16 @@ export type CloudflareSandboxDriverOptions = {
5
5
  workerUrl: string;
6
6
  /** Bearer token the Worker validates (its `API_TOKEN` secret). */
7
7
  apiToken: string;
8
- /** Port the workspace-runtime listens on inside the sandbox (exposed publicly). */
8
+ /** Port the workspace-runtime listens on inside the container, which the Worker's proxy route forwards to. */
9
9
  runtimePort?: number;
10
- /** Command that launches the workspace-runtime inside the image. */
11
10
  runtimeCommand?: string;
12
- /** Workspace directory inside the sandbox. */
13
11
  workspaceDir?: string;
14
12
  /**
15
13
  * Static control-plane config injected into every sandbox so the runtime can
16
- * verify relay-proxied requests. Never carries per-workspace secrets — those
17
- * (if any) belong to the future egress broker.
18
- * relayJwksUrl → WORKSPACE_RUNTIME_RELAY_JWKS_URL (relay public key for RAT verify)
19
- * relayVerifyPem → WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM (fallback to JWKS)
20
- * managementJwksUrl → WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL (config apply)
14
+ * verify relay-proxied requests; never a per-workspace secret.
15
+ * relayJwksUrl → WORKSPACE_RUNTIME_RELAY_JWKS_URL (relay host token keys)
16
+ * relayVerifyPem → WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM (fallback to JWKS)
17
+ * managementJwksUrl → WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL (config apply)
21
18
  */
22
19
  controlEnv?: WorkspaceRuntimeControlEnv;
23
20
  /** Default native harness injected as WORKSPACE_RUNTIME_NATIVE_HARNESS, for example "opencode". */
@@ -26,7 +23,6 @@ export type CloudflareSandboxDriverOptions = {
26
23
  env?: (input: SandboxDriverEnsureInput, sandbox: {
27
24
  id: string;
28
25
  }) => Record<string, string> | Promise<Record<string, string>>;
29
- /** Injected for tests. */
30
26
  fetch?: typeof fetch;
31
27
  /** ensure-runtime/HTTP timeout. */
32
28
  timeoutMs?: number;