@forgezero/agent 0.1.74 → 0.1.75

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 +126 -17
  2. package/dist/agent-heartbeat.d.ts +25 -1
  3. package/dist/agent-heartbeat.js +45 -5
  4. package/dist/bootstrap.js +1 -1
  5. package/dist/community-rehearsal-host.js +208 -9
  6. package/dist/credential-schema.d.ts +1 -1
  7. package/dist/credential-schema.js +1 -1
  8. package/dist/definition.js +206 -7
  9. package/dist/deploy-actions.d.ts +7 -0
  10. package/dist/deploy-compiler.js +191 -10
  11. package/dist/deploy-file.js +206 -7
  12. package/dist/deploy-plan-runner.d.ts +3 -0
  13. package/dist/deploy-plan-runner.js +190 -9
  14. package/dist/deploy-plan.js +187 -8
  15. package/dist/deploy-providers.d.ts +19 -0
  16. package/dist/deploy.d.ts +89 -3
  17. package/dist/deploy.js +49 -2
  18. package/dist/deployment-connectivity.d.ts +63 -0
  19. package/dist/deployment-connectivity.js +148 -0
  20. package/dist/deployment-pull.d.ts +2 -0
  21. package/dist/deployment-targets.d.ts +9 -0
  22. package/dist/deployment-targets.js +141 -0
  23. package/dist/deployment.d.ts +69 -0
  24. package/dist/fz-agent.js +1111 -301
  25. package/dist/fz.js +220 -15
  26. package/dist/index.d.ts +1 -0
  27. package/dist/metal-bootstrap.js +1 -1
  28. package/dist/metal-helper-socket.js +210 -11
  29. package/dist/metal-provision.js +210 -11
  30. package/dist/operator-bootstrap.js +1 -1
  31. package/dist/platform-bootstrap-runtime.js +209 -10
  32. package/dist/platform-fleet-verification.js +515 -149
  33. package/dist/platform-genesis.js +206 -7
  34. package/dist/provision.js +465 -99
  35. package/dist/service-supervisor.d.ts +6 -0
  36. package/dist/software-helper.d.ts +3 -0
  37. package/dist/software-helper.js +465 -98
  38. package/dist/software.d.ts +4 -1
  39. package/dist/software.js +210 -8
  40. package/dist/ubuntu.js +206 -7
  41. package/dist/version.d.ts +1 -1
  42. package/package.json +12 -4
package/dist/fz-agent.js CHANGED
@@ -3,9 +3,9 @@
3
3
 
4
4
  // src/index.ts
5
5
  import { randomBytes as randomBytes7 } from "crypto";
6
- import { readFileSync as readFileSync20, writeFileSync as writeFileSync17, existsSync as existsSync21, mkdirSync as mkdirSync17, chmodSync as chmodSync18, lstatSync as lstatSync9, statfsSync as statfsSync2 } from "fs";
7
- import { cpus, totalmem } from "os";
8
- import { dirname as dirname12, join as join12 } from "path";
6
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync18, existsSync as existsSync22, mkdirSync as mkdirSync18, chmodSync as chmodSync18, lstatSync as lstatSync9, statfsSync as statfsSync3 } from "fs";
7
+ import { cpus as cpus2, totalmem as totalmem2 } from "os";
8
+ import { dirname as dirname14, join as join12 } from "path";
9
9
 
10
10
  // ../access/dist/security.js
11
11
  var HEX = Array.from({ length: 256 }, (_, index) => index.toString(16).padStart(2, "0"));
@@ -4643,7 +4643,7 @@ function safeOp(line) {
4643
4643
  // src/deployment.ts
4644
4644
  import { chmodSync as chmodSync4, existsSync as existsSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, statfsSync, writeFileSync as writeFileSync3 } from "fs";
4645
4645
  import { createHash as createHash5, randomUUID } from "crypto";
4646
- import { dirname, isAbsolute as isAbsolute2, join as join2 } from "path";
4646
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2 } from "path";
4647
4647
 
4648
4648
  // ../runtime/dist/queue.js
4649
4649
  class QueueStoppedError extends Error {
@@ -4953,22 +4953,30 @@ import {
4953
4953
  accessSync,
4954
4954
  chmodSync as chmodSync2,
4955
4955
  copyFileSync,
4956
+ createReadStream,
4956
4957
  existsSync as existsSync2,
4957
4958
  mkdtempSync,
4958
4959
  mkdirSync,
4959
4960
  readFileSync,
4960
4961
  renameSync,
4961
4962
  rmSync,
4963
+ statSync,
4962
4964
  symlinkSync,
4963
4965
  unlinkSync as unlinkSync2,
4964
4966
  writeFileSync
4965
4967
  } from "fs";
4966
4968
  import { tmpdir } from "os";
4967
- import { join } from "path";
4969
+ import { dirname, join } from "path";
4968
4970
  var PINNED_BUN_VERSION = "1.3.14";
4969
4971
  var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
4970
4972
  var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
4971
4973
  var CLOUDFLARED_SHA256 = "9d71c677db00134c1bd4144b7783486b654ad281b1ea62b4972098d19f770f17";
4974
+ var NERDCTL_VERSION = "2.3.5";
4975
+ var BUILDKIT_VERSION = "0.32.2";
4976
+ var KATA_CONTAINERS_VERSION = "4.0.0";
4977
+ var NERDCTL_SHA256 = "de3206aeb7cbd5f20f5fb1f55c1e3bf2db1be567812a8a3f5e65eba2488347ee";
4978
+ var BUILDKIT_SHA256 = "2975d0f651ad96ba8b80b9992ae1f9a964f4408569af5b6dc36544165c3926af";
4979
+ var KATA_SHA256 = "2c3b9dfeba355582b40aee462b12916c9740654d0230f696adf719d67b063a8c";
4972
4980
  var OS_CATALOG = [
4973
4981
  {
4974
4982
  id: "ubuntu",
@@ -4998,6 +5006,8 @@ var OS_CATALOG = [
4998
5006
  var SOFTWARE_CATALOG = [
4999
5007
  { id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5000
5008
  { id: "docker", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5009
+ { id: "containerd", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5010
+ { id: "kata-containers", version: "4.0.0", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5001
5011
  { id: "nginx", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5002
5012
  { id: "arangodb", version: "3.11.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5003
5013
  { id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
@@ -5006,6 +5016,7 @@ var SOFTWARE_CATALOG = [
5006
5016
  { id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5007
5017
  { id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5008
5018
  { id: "docker", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5019
+ { id: "containerd", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5009
5020
  { id: "nginx", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5010
5021
  { id: "ufw", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
5011
5022
  { id: "openssh-client", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
@@ -5014,6 +5025,8 @@ var SOFTWARE_CATALOG = [
5014
5025
  var UBUNTU_2604_X64 = [
5015
5026
  { requirement: { id: "bun", version: "1.3.14" } },
5016
5027
  { requirement: { id: "docker", version: "ubuntu-26.04" } },
5028
+ { requirement: { id: "containerd", version: "ubuntu-26.04" } },
5029
+ { requirement: { id: "kata-containers", version: "4.0.0" } },
5017
5030
  { requirement: { id: "nginx", version: "ubuntu-26.04" } },
5018
5031
  { requirement: { id: "arangodb", version: "3.11.14" } },
5019
5032
  { requirement: { id: "cloudflared", version: "2026.7.3" } },
@@ -5025,6 +5038,7 @@ var UBUNTU_2604_X64 = [
5025
5038
  var UBUNTU_2404_X64 = [
5026
5039
  { requirement: { id: "bun", version: "1.3.14" } },
5027
5040
  { requirement: { id: "docker", version: "ubuntu-24.04" } },
5041
+ { requirement: { id: "containerd", version: "ubuntu-24.04" } },
5028
5042
  { requirement: { id: "nginx", version: "ubuntu-24.04" } },
5029
5043
  { requirement: { id: "ufw", version: "ubuntu-24.04" } },
5030
5044
  { requirement: { id: "openssh-client", version: "ubuntu-24.04" } },
@@ -5041,6 +5055,46 @@ var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
5041
5055
  "log-opts": { "max-size": "10m", "max-file": "3" }
5042
5056
  }, null, 2)}
5043
5057
  `;
5058
+ var CONTAINERD_CONFIG_PATH = "/etc/containerd/config.toml";
5059
+ var KATA_SNP_CONFIG_PATH = "/opt/kata/share/defaults/kata-containers/configuration-qemu-snp.toml";
5060
+ var KATA_ACTIVE_CONFIG_PATH = "/etc/kata-containers/configuration.toml";
5061
+ var CONTAINERD_CONFIG = (kata) => `version = 2
5062
+ root = "/var/lib/forgezero/containerd"
5063
+ state = "/run/forgezero/containerd"
5064
+
5065
+ [grpc]
5066
+ address = "/run/containerd/containerd.sock"
5067
+
5068
+ [plugins."io.containerd.grpc.v1.cri".containerd]
5069
+ snapshotter = "overlayfs"
5070
+ default_runtime_name = "runc"
5071
+
5072
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
5073
+ runtime_type = "io.containerd.runc.v2"
5074
+ ${kata ? `
5075
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu-snp]
5076
+ runtime_type = "io.containerd.kata.v2"
5077
+ [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu-snp.options]
5078
+ ConfigPath = "${KATA_ACTIVE_CONFIG_PATH}"
5079
+ ` : ""}`;
5080
+ var BUILDKIT_UNIT = `[Unit]
5081
+ Description=ForgeZero BuildKit daemon
5082
+ After=network-online.target containerd.service
5083
+ Wants=network-online.target
5084
+ Requires=containerd.service
5085
+
5086
+ [Service]
5087
+ Type=notify
5088
+ ExecStart=/usr/local/bin/buildkitd --addr unix:///run/buildkit/buildkitd.sock --root /var/lib/forgezero/buildkit
5089
+ Restart=on-failure
5090
+ RestartSec=2
5091
+ NoNewPrivileges=yes
5092
+ ProtectSystem=strict
5093
+ ReadWritePaths=/var/lib/forgezero/buildkit /run/buildkit
5094
+
5095
+ [Install]
5096
+ WantedBy=multi-user.target
5097
+ `;
5044
5098
  var softwareSpawnFailure = (cause, argv) => cause.code === "ENOENT" ? { exitCode: 127, output: `executable is absent: ${argv[0]}` } : undefined;
5045
5099
  var runSoftwareCommand = async (argv, env = {}) => {
5046
5100
  let child;
@@ -5060,14 +5114,28 @@ var runSoftwareCommand = async (argv, env = {}) => {
5060
5114
  return { exitCode, output: `${stdout}${stderr}` };
5061
5115
  };
5062
5116
  var run = runSoftwareCommand;
5063
- var download = async (url, destination, sha2562) => {
5064
- const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120000) });
5117
+ var download = async (url, destination, sha2562, maximumBytes = 512 * 1024 * 1024) => {
5118
+ const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(30 * 60000) });
5065
5119
  if (!response.ok)
5066
5120
  throw new Error(`download failed with HTTP ${response.status}`);
5067
- const bytes = new Uint8Array(await response.arrayBuffer());
5068
- if (createHash("sha256").update(bytes).digest("hex") !== sha2562)
5121
+ const declared = Number(response.headers.get("content-length") ?? 0);
5122
+ if (declared > maximumBytes)
5123
+ throw new Error("download exceeds reviewed size bound");
5124
+ if (existsSync2(destination))
5125
+ throw new Error("download destination already exists");
5126
+ await Bun.write(destination, response);
5127
+ chmodSync2(destination, 384);
5128
+ if (statSync(destination).size > maximumBytes) {
5129
+ rmSync(destination, { force: true });
5130
+ throw new Error("download exceeds reviewed size bound");
5131
+ }
5132
+ const digest = createHash("sha256");
5133
+ for await (const chunk of createReadStream(destination))
5134
+ digest.update(chunk);
5135
+ if (digest.digest("hex") !== sha2562) {
5136
+ rmSync(destination, { force: true });
5069
5137
  throw new Error("download checksum mismatch");
5070
- writeFileSync(destination, bytes, { mode: 384, flag: "wx" });
5138
+ }
5071
5139
  };
5072
5140
  var successful = (result, pattern) => result.exitCode === 0 && (!pattern || pattern.test(result.output));
5073
5141
  var aptInstall = async (name) => {
@@ -5075,6 +5143,106 @@ var aptInstall = async (name) => {
5075
5143
  const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
5076
5144
  return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", name], environment) : update;
5077
5145
  };
5146
+ var aptInstallMany = async (names) => {
5147
+ const environment = { DEBIAN_FRONTEND: "noninteractive" };
5148
+ const update = await run(["/usr/bin/apt-get", "update", "-qq"], environment);
5149
+ return update.exitCode === 0 ? run(["/usr/bin/apt-get", "install", "-y", ...names], environment) : update;
5150
+ };
5151
+ var writeOwnedPolicy = (pathValue, content, mode = 420) => {
5152
+ if (existsSync2(pathValue)) {
5153
+ if (readFileSync(pathValue, "utf8") !== content)
5154
+ throw new Error(`refusing to overwrite non-ForgeZero policy at ${pathValue}`);
5155
+ return;
5156
+ }
5157
+ mkdirSync(dirname(pathValue), { recursive: true, mode: 493 });
5158
+ writeFileSync(pathValue, content, { mode, flag: "wx" });
5159
+ };
5160
+ var installContainerdRuntime = async (directory, osVersion) => {
5161
+ const installed = await aptInstallMany(["containerd", "runc", "containernetworking-plugins"]);
5162
+ if (installed.exitCode !== 0)
5163
+ return installed;
5164
+ const nerdctl = join(directory, "nerdctl.tar.gz");
5165
+ await download(`https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz`, nerdctl, NERDCTL_SHA256);
5166
+ const nerdctlExtract = await run(["/usr/bin/tar", "-xzf", nerdctl, "-C", "/usr/local/bin", "nerdctl"]);
5167
+ if (nerdctlExtract.exitCode !== 0)
5168
+ return nerdctlExtract;
5169
+ chmodSync2("/usr/local/bin/nerdctl", 493);
5170
+ const buildkit = join(directory, "buildkit.tar.gz");
5171
+ await download(`https://github.com/moby/buildkit/releases/download/v${BUILDKIT_VERSION}/buildkit-v${BUILDKIT_VERSION}.linux-amd64.tar.gz`, buildkit, BUILDKIT_SHA256);
5172
+ const unpacked = join(directory, "buildkit");
5173
+ mkdirSync(unpacked, { mode: 448 });
5174
+ const buildkitExtract = await run(["/usr/bin/tar", "-xzf", buildkit, "-C", unpacked]);
5175
+ if (buildkitExtract.exitCode !== 0)
5176
+ return buildkitExtract;
5177
+ for (const binary of ["buildctl", "buildkitd"]) {
5178
+ copyFileSync(join(unpacked, "bin", binary), `/usr/local/bin/${binary}`);
5179
+ chmodSync2(`/usr/local/bin/${binary}`, 493);
5180
+ }
5181
+ mkdirSync("/etc/containerd", { recursive: true, mode: 493 });
5182
+ const expected = CONTAINERD_CONFIG(false);
5183
+ const kataExpected = CONTAINERD_CONFIG(true);
5184
+ if (existsSync2(CONTAINERD_CONFIG_PATH) && ![expected, kataExpected].includes(readFileSync(CONTAINERD_CONFIG_PATH, "utf8"))) {
5185
+ return { exitCode: 2, output: "refusing to overwrite an existing containerd configuration that differs from the reviewed ForgeZero policy" };
5186
+ }
5187
+ if (!existsSync2(CONTAINERD_CONFIG_PATH))
5188
+ writeFileSync(CONTAINERD_CONFIG_PATH, expected, { mode: 420, flag: "wx" });
5189
+ writeOwnedPolicy("/etc/systemd/system/forgezero-buildkit.service", BUILDKIT_UNIT);
5190
+ mkdirSync("/var/lib/forgezero/containerd", { recursive: true, mode: 448 });
5191
+ mkdirSync("/var/lib/forgezero/buildkit", { recursive: true, mode: 448 });
5192
+ const reload = await run(["/usr/bin/systemctl", "daemon-reload"]);
5193
+ if (reload.exitCode !== 0)
5194
+ return reload;
5195
+ const enabled = await run(["/usr/bin/systemctl", "enable", "--now", "containerd.service", "forgezero-buildkit.service"]);
5196
+ return enabled.exitCode === 0 ? { exitCode: 0, output: `containerd ${osVersion} with nerdctl ${NERDCTL_VERSION} and BuildKit ${BUILDKIT_VERSION}` } : enabled;
5197
+ };
5198
+ var installKataRuntime = async (directory) => {
5199
+ for (const pathValue of ["/dev/kvm", "/dev/sev"])
5200
+ if (!existsSync2(pathValue))
5201
+ return { exitCode: 2, output: `${pathValue} is required for Kata SEV-SNP` };
5202
+ for (const parameter of ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"]) {
5203
+ if (!existsSync2(parameter) || !/^(1|Y)$/i.test(readFileSync(parameter, "utf8").trim()))
5204
+ return { exitCode: 2, output: `${parameter} does not enable SEV-SNP` };
5205
+ }
5206
+ const dependencies = await aptInstallMany(["zstd"]);
5207
+ if (dependencies.exitCode !== 0)
5208
+ return dependencies;
5209
+ const archive = join(directory, "kata.tar.zst");
5210
+ await download(`https://github.com/kata-containers/kata-containers/releases/download/${KATA_CONTAINERS_VERSION}/kata-static-${KATA_CONTAINERS_VERSION}-amd64.tar.zst`, archive, KATA_SHA256, 2200000000);
5211
+ const tar = join(directory, "kata.tar");
5212
+ const expanded = await run(["/usr/bin/unzstd", "--quiet", archive, "--output", tar]);
5213
+ if (expanded.exitCode !== 0)
5214
+ return expanded;
5215
+ const extracted = await run(["/usr/bin/tar", "-xf", tar, "-C", "/"]);
5216
+ if (extracted.exitCode !== 0)
5217
+ return extracted;
5218
+ if (!existsSync2("/opt/kata/bin/containerd-shim-kata-v2") || !existsSync2(KATA_SNP_CONFIG_PATH)) {
5219
+ return { exitCode: 2, output: "Kata archive does not contain the reviewed QEMU SNP runtime and configuration" };
5220
+ }
5221
+ const snpConfig = readFileSync(KATA_SNP_CONFIG_PATH, "utf8");
5222
+ if (!/^\s*confidential_guest\s*=\s*true\s*$/m.test(snpConfig) || !/^\s*sev_snp_guest\s*=\s*true\s*$/m.test(snpConfig)) {
5223
+ return { exitCode: 2, output: "Kata QEMU configuration does not enable confidential SEV-SNP guests" };
5224
+ }
5225
+ mkdirSync("/etc/kata-containers", { recursive: true, mode: 493 });
5226
+ if (existsSync2(KATA_ACTIVE_CONFIG_PATH) && readFileSync(KATA_ACTIVE_CONFIG_PATH, "utf8") !== snpConfig) {
5227
+ return { exitCode: 2, output: "refusing to overwrite a non-ForgeZero Kata runtime configuration" };
5228
+ }
5229
+ if (!existsSync2(KATA_ACTIVE_CONFIG_PATH))
5230
+ copyFileSync(KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH);
5231
+ chmodSync2(KATA_ACTIVE_CONFIG_PATH, 420);
5232
+ try {
5233
+ unlinkSync2("/usr/local/bin/containerd-shim-kata-v2");
5234
+ } catch {}
5235
+ symlinkSync("/opt/kata/bin/containerd-shim-kata-v2", "/usr/local/bin/containerd-shim-kata-v2");
5236
+ const base = CONTAINERD_CONFIG(false);
5237
+ const kata = CONTAINERD_CONFIG(true);
5238
+ if (!existsSync2(CONTAINERD_CONFIG_PATH) || ![base, kata].includes(readFileSync(CONTAINERD_CONFIG_PATH, "utf8"))) {
5239
+ return { exitCode: 2, output: "containerd must use the reviewed ForgeZero policy before Kata is installed" };
5240
+ }
5241
+ if (readFileSync(CONTAINERD_CONFIG_PATH, "utf8") !== kata)
5242
+ writeFileSync(CONTAINERD_CONFIG_PATH, kata, { mode: 420 });
5243
+ const restarted = await run(["/usr/bin/systemctl", "restart", "containerd.service"]);
5244
+ return restarted.exitCode === 0 ? { exitCode: 0, output: `Kata Containers ${KATA_CONTAINERS_VERSION} QEMU SNP` } : restarted;
5245
+ };
5078
5246
  async function executeSoftwareOperation(operation) {
5079
5247
  const { software, version } = operation;
5080
5248
  if (![...UBUNTU_2604_X64, ...UBUNTU_2404_X64].some(({ requirement }) => requirement.id === software && requirement.version === version)) {
@@ -5092,6 +5260,26 @@ async function executeSoftwareOperation(operation) {
5092
5260
  const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "docker.service"]);
5093
5261
  return active.exitCode === 0 ? { exitCode: 0, output: binary.output } : active;
5094
5262
  }
5263
+ if (software === "containerd") {
5264
+ const [containerd, nerdctl, buildkit, active, builder] = await Promise.all([
5265
+ run(["/usr/bin/containerd", "--version"]),
5266
+ run(["/usr/local/bin/nerdctl", "--version"]),
5267
+ run(["/usr/local/bin/buildkitd", "--version"]),
5268
+ run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]),
5269
+ run(["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-buildkit.service"])
5270
+ ]);
5271
+ const policy = existsSync2(CONTAINERD_CONFIG_PATH) ? readFileSync(CONTAINERD_CONFIG_PATH, "utf8") : "";
5272
+ const validPolicy = policy === CONTAINERD_CONFIG(false) || policy === CONTAINERD_CONFIG(true);
5273
+ return successful(containerd, /containerd/) && successful(nerdctl, new RegExp(NERDCTL_VERSION.replaceAll(".", "\\."))) && successful(buildkit, new RegExp(BUILDKIT_VERSION.replaceAll(".", "\\."))) && active.exitCode === 0 && builder.exitCode === 0 && validPolicy ? { exitCode: 0, output: `${containerd.output}${nerdctl.output}${buildkit.output}` } : { exitCode: 1, output: "containerd, nerdctl, BuildKit, or ForgeZero storage/runtime policy is unavailable" };
5274
+ }
5275
+ if (software === "kata-containers") {
5276
+ const shim = await run(["/opt/kata/bin/containerd-shim-kata-v2", "--version"]);
5277
+ const policy = existsSync2(CONTAINERD_CONFIG_PATH) ? readFileSync(CONTAINERD_CONFIG_PATH, "utf8") : "";
5278
+ const activeConfig = existsSync2(KATA_ACTIVE_CONFIG_PATH) ? readFileSync(KATA_ACTIVE_CONFIG_PATH, "utf8") : "";
5279
+ const host = ["/dev/kvm", "/dev/sev", KATA_SNP_CONFIG_PATH, KATA_ACTIVE_CONFIG_PATH].every(existsSync2) && ["/sys/module/kvm_amd/parameters/sev", "/sys/module/kvm_amd/parameters/sev_snp"].every((parameter) => existsSync2(parameter) && /^(1|Y)$/i.test(readFileSync(parameter, "utf8").trim()));
5280
+ const active = await run(["/usr/bin/systemctl", "is-active", "--quiet", "containerd.service"]);
5281
+ return successful(shim, /kata.*4\.0\.0/i) && policy === CONTAINERD_CONFIG(true) && activeConfig === (existsSync2(KATA_SNP_CONFIG_PATH) ? readFileSync(KATA_SNP_CONFIG_PATH, "utf8") : "") && host && active.exitCode === 0 ? { exitCode: 0, output: shim.output } : { exitCode: 1, output: "Kata QEMU SNP runtime, host capability, or containerd policy is unavailable" };
5282
+ }
5095
5283
  if (software === "nginx") {
5096
5284
  const binary = await run(["/usr/sbin/nginx", "-v"]);
5097
5285
  return binary.exitCode === 0 ? run(["/usr/bin/systemctl", "is-active", "--quiet", "nginx.service"]) : binary;
@@ -5143,6 +5331,17 @@ async function executeSoftwareOperation(operation) {
5143
5331
  }
5144
5332
  const directory = mkdtempSync(join(tmpdir(), "forgezero-software-"));
5145
5333
  try {
5334
+ if (software === "containerd")
5335
+ return installContainerdRuntime(directory, version === "ubuntu-24.04" ? "24.04" : "26.04");
5336
+ if (software === "kata-containers") {
5337
+ const containerd = await executeSoftwareOperation({ kind: "check", software: "containerd", version: "ubuntu-26.04" });
5338
+ if (containerd.exitCode !== 0) {
5339
+ const installed2 = await installContainerdRuntime(directory, "26.04");
5340
+ if (installed2.exitCode !== 0)
5341
+ return installed2;
5342
+ }
5343
+ return installKataRuntime(directory);
5344
+ }
5146
5345
  if (software === "cloudflare-warp") {
5147
5346
  const key = join(directory, "cloudflare-warp-key.gpg");
5148
5347
  await download("https://pkg.cloudflareclient.com/pubkey.gpg", key, "0f37fc298c98e88ee3c0ee68c95b69f1dba9eb477abe3167e13982105911264d");
@@ -5224,7 +5423,7 @@ function validateSoftwareRequirements(value, _options = {}) {
5224
5423
  if (Object.keys(row).some((key) => key !== "id" && key !== "version")) {
5225
5424
  throw new Error("software requirement contains an unknown field");
5226
5425
  }
5227
- if (!["bun", "docker", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client", "git"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
5426
+ if (!["bun", "docker", "containerd", "kata-containers", "nginx", "arangodb", "cloudflared", "cloudflare-warp", "ufw", "openssh-client", "git"].includes(String(row.id)) || typeof row.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9.-]{0,31}$/.test(row.version)) {
5228
5427
  throw new Error("software requirement coordinate is invalid");
5229
5428
  }
5230
5429
  const requirement = { id: row.id, version: row.version };
@@ -5998,6 +6197,13 @@ function validateBuiltInAction(definition, context, where) {
5998
6197
  throw new Error(`${where}.with.requirements must name existing requirements`);
5999
6198
  return;
6000
6199
  }
6200
+ if (definition.uses === "forgezero.network/ensure@1") {
6201
+ exact(withValue, ["target"], `${where}.with`);
6202
+ if (typeof withValue.target !== "string" || !Object.values(context.components).some((entry) => entry.target === withValue.target)) {
6203
+ throw new Error(`${where}.with.target must name a target used by a component`);
6204
+ }
6205
+ return;
6206
+ }
6001
6207
  if (definition.uses === "forgezero.exec/argv@1") {
6002
6208
  const allowed = ["component", "argv", "credentials"];
6003
6209
  const unknown = Object.keys(withValue).filter((key) => !allowed.includes(key));
@@ -6053,6 +6259,8 @@ function validateBuiltInProvider(requirement2, where) {
6053
6259
  const expected = {
6054
6260
  "forgezero.bun": { capability: "runtime.javascript", versions: ["1.3.14"], keys: ["installScope"] },
6055
6261
  "forgezero.docker": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "storageDriver", "dataRoot", "liveRestore", "logDriver", "defaultNetwork", "rootless"] },
6262
+ "forgezero.containerd": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "runtimeClass", "namespace", "snapshotter"] },
6263
+ "forgezero.kata": { capability: "runtime.container", versions: ["4.0.0"], keys: ["installScope", "runtimeClass", "namespace", "hypervisor", "confidentialGuest"] },
6056
6264
  "forgezero.nginx": { capability: "proxy.http", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "websocket", "maximumBodyMiB", "workerProcesses", "workerConnections"] },
6057
6265
  "forgezero.arangodb": { capability: "database.arangodb", versions: ["3.11.14"], keys: ["installScope", "runtime", "dataPath", "bind"] },
6058
6266
  "forgezero.cloudflared": { capability: "network.public-tunnel", versions: ["2026.7.3"], keys: ["installScope", "mode"] },
@@ -6073,6 +6281,10 @@ function validateBuiltInProvider(requirement2, where) {
6073
6281
  if (row2.storageDriver !== "overlay2" || row2.dataRoot !== "/var/lib/docker" || row2.liveRestore !== true || row2.logDriver !== "local" || row2.defaultNetwork !== "bridge" || row2.rootless !== false)
6074
6282
  throw new Error(`${where} Docker config is unsupported by the fixed host strategy`);
6075
6283
  }
6284
+ if (requirement2.provider === "forgezero.containerd" && (row2.runtimeClass !== "runc" || row2.namespace !== "forgezero" || row2.snapshotter !== "overlayfs"))
6285
+ throw new Error(`${where} containerd config is unsupported by the fixed host strategy`);
6286
+ if (requirement2.provider === "forgezero.kata" && (row2.runtimeClass !== "kata-qemu-snp" || row2.namespace !== "forgezero" || row2.hypervisor !== "qemu" || row2.confidentialGuest !== "sev-snp"))
6287
+ throw new Error(`${where} Kata config is unsupported by the fixed host strategy`);
6076
6288
  if (requirement2.provider === "forgezero.nginx" && (typeof row2.websocket !== "boolean" || !Number.isInteger(row2.maximumBodyMiB) || Number(row2.maximumBodyMiB) < 1 || Number(row2.maximumBodyMiB) > 1024 || !(row2.workerProcesses === "auto" || Number.isInteger(row2.workerProcesses)) || !Number.isInteger(row2.workerConnections)))
6077
6289
  throw new Error(`${where} Nginx config is invalid`);
6078
6290
  if (requirement2.provider === "forgezero.arangodb" && (row2.runtime !== "native" || !absolute(row2.dataPath) || row2.bind !== "private"))
@@ -6150,7 +6362,7 @@ function reference(value, where) {
6150
6362
  const row2 = object(value, where);
6151
6363
  exact3(row2, ["$ref"], where);
6152
6364
  const coordinate = boundedString(row2.$ref, `${where}.$ref`, 256);
6153
- if (!/^(inputs\.[a-z][A-Za-z0-9-]{0,62}|steps\.[a-z][A-Za-z0-9-]{0,62}\.(status|outputs\.[a-z][A-Za-z0-9-]{0,62})|components\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|targets\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|deployment\.[a-z][A-Za-z0-9-]{0,62})$/.test(coordinate)) {
6365
+ if (!/^(inputs\.[a-z][A-Za-z0-9-]{0,62}|steps\.[a-z][A-Za-z0-9-]{0,62}\.(status|outputs\.[a-z][A-Za-z0-9-]{0,62})|components\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|targets\.[a-z][A-Za-z0-9-]{0,62}\.[a-z][A-Za-z0-9-]{0,62}|deployment\.[a-z][A-Za-z0-9-]{0,62}|execution\.(targetName|slot|releaseExecutor|runtime|cpuCores|memoryMiB|storageGiB))$/.test(coordinate)) {
6154
6366
  fail(where, "contains an unsupported reference.");
6155
6367
  }
6156
6368
  return { $ref: coordinate };
@@ -6390,10 +6602,9 @@ function validateNetwork(value, where) {
6390
6602
  named(container.network, `${where}.container.network`);
6391
6603
  }
6392
6604
  }
6393
- function validateComponent(value, where, targetNames, requirements) {
6605
+ function validateComponent(value, where, targets, requirements) {
6394
6606
  const row2 = object(value, where);
6395
- if (!targetNames.has(String(row2.target)))
6396
- fail(`${where}.target`, "must name an existing target.");
6607
+ const selectedTarget = targets.get(String(row2.target)) ?? fail(`${where}.target`, "must name an existing target.");
6397
6608
  if (row2.kind === "application") {
6398
6609
  exact3(row2, ["kind", "target", "runtime", "service", "resources", "storage", "network", "rollout"], where);
6399
6610
  const runtime = object(row2.runtime, `${where}.runtime`);
@@ -6404,7 +6615,9 @@ function validateComponent(value, where, targetNames, requirements) {
6404
6615
  exact3(runtime, ["kind", "provider", "requirement", "argv"], `${where}.runtime`);
6405
6616
  validateArgv(runtime.argv, `${where}.runtime.argv`);
6406
6617
  } else if (runtime.kind === "container") {
6407
- exact3(runtime, ["kind", "provider", "requirement", "image", "entrypoint", "security"], `${where}.runtime`);
6618
+ exact3(runtime, ["kind", "provider", "requirement", "runtimeClass", "image", "entrypoint", "security"], `${where}.runtime`);
6619
+ if (runtime.runtimeClass !== undefined && !["runc", "kata-qemu-snp"].includes(String(runtime.runtimeClass)))
6620
+ fail(`${where}.runtime.runtimeClass`, "is unsupported.");
6408
6621
  const image = object(runtime.image, `${where}.runtime.image`);
6409
6622
  exact3(image, ["source"], `${where}.runtime.image`);
6410
6623
  const source = object(image.source, `${where}.runtime.image.source`);
@@ -6438,6 +6651,23 @@ function validateComponent(value, where, targetNames, requirements) {
6438
6651
  fail(`${where}.runtime.requirement`, "must provide runtime.container.");
6439
6652
  if (runtimeRequirement && runtime.kind === "native" && !runtimeRequirement.capability.startsWith("runtime."))
6440
6653
  fail(`${where}.runtime.requirement`, "must provide a runtime capability.");
6654
+ const execution = selectedTarget.allocation.execution;
6655
+ if (execution === "native" && runtime.kind !== "native")
6656
+ fail(`${where}.runtime.kind`, "must be native for the selected target.");
6657
+ if (execution !== "native" && runtime.kind !== "container")
6658
+ fail(`${where}.runtime.kind`, "must be container for the selected target.");
6659
+ if (runtime.kind === "container" && execution === "oci-runc") {
6660
+ if (!["forgezero.containerd", "forgezero.docker"].includes(String(runtime.provider)))
6661
+ fail(`${where}.runtime.provider`, "must be forgezero.containerd for an OCI runc target.");
6662
+ if (runtime.runtimeClass !== undefined && runtime.runtimeClass !== "runc")
6663
+ fail(`${where}.runtime.runtimeClass`, "must be runc for the selected target.");
6664
+ }
6665
+ if (runtime.kind === "container" && execution === "oci-kata-qemu-snp") {
6666
+ if (runtime.provider !== "forgezero.kata")
6667
+ fail(`${where}.runtime.provider`, "must be forgezero.kata for a Kata QEMU SNP target.");
6668
+ if (runtime.runtimeClass !== "kata-qemu-snp")
6669
+ fail(`${where}.runtime.runtimeClass`, "must be kata-qemu-snp for the selected target.");
6670
+ }
6441
6671
  const service = object(row2.service, `${where}.service`);
6442
6672
  exact3(service, ["protocol", "port", "health", "websocket", "maximumConnections"], `${where}.service`);
6443
6673
  if (service.protocol !== "http")
@@ -6469,6 +6699,18 @@ function validateComponent(value, where, targetNames, requirements) {
6469
6699
  const network = object(row2.network, `${where}.network`);
6470
6700
  if (network.container === undefined)
6471
6701
  fail(`${where}.network.container`, "must select bridge or reviewed host networking.");
6702
+ if (selectedTarget.allocation.sharing === "shared") {
6703
+ if (object(runtime.security, `${where}.runtime.security`).root !== "read-only")
6704
+ fail(`${where}.runtime.security.root`, "must be read-only on a shared compute.");
6705
+ if (network.container.mode !== "bridge")
6706
+ fail(`${where}.network.container.mode`, "must be bridge on a shared compute.");
6707
+ if (Array.isArray(row2.storage) && row2.storage.some((mount) => mount.class !== "ephemeral")) {
6708
+ fail(`${where}.storage`, "may contain only size-bounded tmpfs mounts on a shared compute.");
6709
+ }
6710
+ }
6711
+ }
6712
+ if (runtime.kind === "native" && selectedTarget.allocation.sharing !== "exclusive") {
6713
+ fail(`${where}.target`, "native applications require an exclusive compute.");
6472
6714
  }
6473
6715
  if (row2.storage !== undefined) {
6474
6716
  if (!Array.isArray(row2.storage) || row2.storage.length > 32)
@@ -6505,6 +6747,8 @@ function validateComponent(value, where, targetNames, requirements) {
6505
6747
  fail(`${where}.runtime.mode`, "must match the provider runtime mode.");
6506
6748
  if (runtime.mode !== "native")
6507
6749
  fail(`${where}.runtime.mode`, "must be native in deployment plan v1.");
6750
+ if (selectedTarget.allocation.execution !== "native")
6751
+ fail(`${where}.target`, "must select a native execution target for database components.");
6508
6752
  const topology = object(row2.topology, `${where}.topology`);
6509
6753
  if (topology.mode === "standalone")
6510
6754
  exact3(topology, ["mode"], `${where}.topology`);
@@ -6701,15 +6945,20 @@ function compileDeployment(sourceValue) {
6701
6945
  if (targetEntries.length < 1 || targetEntries.length > 64)
6702
6946
  fail("deployment.spec.targets", "must contain 1 to 64 targets.");
6703
6947
  const targetNames = new Set(targetEntries.map(([targetName]) => named(targetName, "deployment.spec.targets key")));
6948
+ const targetDefinitions = new Map;
6704
6949
  for (const [targetName, value] of targetEntries) {
6705
6950
  const target = object(value, `deployment.spec.targets.${targetName}`);
6706
- exact3(target, ["kind", "selector", "cardinality", "placement"], `deployment.spec.targets.${targetName}`);
6951
+ exact3(target, ["kind", "selector", "cardinality", "allocation", "provisioning", "connectivity", "placement"], `deployment.spec.targets.${targetName}`);
6707
6952
  if (target.kind !== "compute")
6708
6953
  fail(`deployment.spec.targets.${targetName}.kind`, "must be compute.");
6709
6954
  const selector = object(target.selector, `deployment.spec.targets.${targetName}.selector`);
6710
- exact3(selector, ["profiles", "confidentialCompute", "labels"], `deployment.spec.targets.${targetName}.selector`);
6955
+ exact3(selector, ["profiles", "operatingSystem", "confidentialCompute", "labels"], `deployment.spec.targets.${targetName}.selector`);
6711
6956
  if (!Array.isArray(selector.profiles) || selector.profiles.length < 1 || selector.profiles.length > 32 || selector.profiles.some((profile) => typeof profile !== "string" || !NAME2.test(profile)) || new Set(selector.profiles).size !== selector.profiles.length)
6712
6957
  fail(`deployment.spec.targets.${targetName}.selector.profiles`, "is invalid.");
6958
+ const operatingSystem = object(selector.operatingSystem, `deployment.spec.targets.${targetName}.selector.operatingSystem`);
6959
+ exact3(operatingSystem, ["id", "version", "architecture"], `deployment.spec.targets.${targetName}.selector.operatingSystem`);
6960
+ if (operatingSystem.id !== "ubuntu" || !["24.04", "26.04"].includes(String(operatingSystem.version)) || operatingSystem.architecture !== "x64")
6961
+ fail(`deployment.spec.targets.${targetName}.selector.operatingSystem`, "is unsupported.");
6713
6962
  if (selector.confidentialCompute !== undefined && !["required", "preferred", "disabled"].includes(String(selector.confidentialCompute)))
6714
6963
  fail(`deployment.spec.targets.${targetName}.selector.confidentialCompute`, "is unsupported.");
6715
6964
  if (selector.labels !== undefined)
@@ -6722,8 +6971,108 @@ function compileDeployment(sourceValue) {
6722
6971
  integer(cardinality.desired, `deployment.spec.targets.${targetName}.cardinality.desired`, minimum, maximum);
6723
6972
  else
6724
6973
  reference(cardinality.desired, `deployment.spec.targets.${targetName}.cardinality.desired`);
6974
+ const allocation = object(target.allocation, `deployment.spec.targets.${targetName}.allocation`);
6975
+ exact3(allocation, ["execution", "sharing", "resources", "existing"], `deployment.spec.targets.${targetName}.allocation`);
6976
+ if (!["native", "oci-runc", "oci-kata-qemu-snp"].includes(String(allocation.execution)))
6977
+ fail(`deployment.spec.targets.${targetName}.allocation.execution`, "is unsupported.");
6978
+ if (!["exclusive", "shared"].includes(String(allocation.sharing)))
6979
+ fail(`deployment.spec.targets.${targetName}.allocation.sharing`, "is unsupported.");
6980
+ if (!["prefer", "require"].includes(String(allocation.existing)))
6981
+ fail(`deployment.spec.targets.${targetName}.allocation.existing`, "is unsupported.");
6982
+ const capacity = object(allocation.resources, `deployment.spec.targets.${targetName}.allocation.resources`);
6983
+ exact3(capacity, ["cpuCores", "memoryMiB", "storageGiB"], `deployment.spec.targets.${targetName}.allocation.resources`);
6984
+ const capacityValue = (value2, where, minimumValue, maximumValue) => {
6985
+ if (typeof value2 === "number")
6986
+ integer(value2, where, minimumValue, maximumValue);
6987
+ else
6988
+ reference(value2, where);
6989
+ };
6990
+ capacityValue(capacity.cpuCores, `deployment.spec.targets.${targetName}.allocation.resources.cpuCores`, 1, 1024);
6991
+ capacityValue(capacity.memoryMiB, `deployment.spec.targets.${targetName}.allocation.resources.memoryMiB`, 128, 4194304);
6992
+ capacityValue(capacity.storageGiB, `deployment.spec.targets.${targetName}.allocation.resources.storageGiB`, 1, 1048576);
6993
+ if (allocation.execution === "oci-kata-qemu-snp" && selector.confidentialCompute !== "required")
6994
+ fail(`deployment.spec.targets.${targetName}.selector.confidentialCompute`, "must be required for Kata QEMU SNP.");
6995
+ if ((allocation.execution === "oci-kata-qemu-snp" || selector.confidentialCompute === "required") && operatingSystem.version !== "26.04")
6996
+ fail(`deployment.spec.targets.${targetName}.selector.operatingSystem.version`, "must be Ubuntu 26.04 for SEV-SNP.");
6997
+ if (allocation.execution !== "oci-kata-qemu-snp" && selector.confidentialCompute === "required" && allocation.execution !== "native")
6998
+ fail(`deployment.spec.targets.${targetName}.allocation.execution`, "cannot satisfy required confidential compute.");
6999
+ if (target.provisioning !== undefined) {
7000
+ const provisioning = object(target.provisioning, `deployment.spec.targets.${targetName}.provisioning`);
7001
+ exact3(provisioning, ["planKey", "regionKey", "imageKey", "environmentKey", "ownership", "metalHostname", "maxMonthlySpendMinor"], `deployment.spec.targets.${targetName}.provisioning`);
7002
+ for (const key of ["planKey", "regionKey", "imageKey", "environmentKey"])
7003
+ named(provisioning[key], `deployment.spec.targets.${targetName}.provisioning.${key}`);
7004
+ if (!["platform", "tenant-metal"].includes(String(provisioning.ownership)) || provisioning.ownership === "tenant-metal" !== Boolean(provisioning.metalHostname))
7005
+ fail(`deployment.spec.targets.${targetName}.provisioning.ownership`, "must bind platform capacity or one tenant metal hostname.");
7006
+ if (provisioning.metalHostname !== undefined && !/^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(String(provisioning.metalHostname)))
7007
+ fail(`deployment.spec.targets.${targetName}.provisioning.metalHostname`, "is invalid.");
7008
+ if (typeof provisioning.maxMonthlySpendMinor !== "string" || !/^\d{1,18}$/.test(provisioning.maxMonthlySpendMinor))
7009
+ fail(`deployment.spec.targets.${targetName}.provisioning.maxMonthlySpendMinor`, "must be a bounded decimal minor-unit amount.");
7010
+ }
7011
+ if (allocation.existing === "prefer" && target.provisioning === undefined)
7012
+ fail(`deployment.spec.targets.${targetName}.provisioning`, "is required when placement may provision after reuse.");
7013
+ if (allocation.existing === "require" && target.provisioning !== undefined)
7014
+ fail(`deployment.spec.targets.${targetName}.provisioning`, "must be omitted for reuse-only placement.");
7015
+ if (target.connectivity !== undefined) {
7016
+ const connectivity = object(target.connectivity, `deployment.spec.targets.${targetName}.connectivity`);
7017
+ exact3(connectivity, ["private", "public"], `deployment.spec.targets.${targetName}.connectivity`);
7018
+ const declaredPrivate = connectivity.private === undefined ? undefined : object(connectivity.private, `deployment.spec.targets.${targetName}.connectivity.private`);
7019
+ if (declaredPrivate?.mode === "cloudflare-warp" && allocation.sharing !== "exclusive")
7020
+ fail(`deployment.spec.targets.${targetName}.allocation.sharing`, "must be exclusive for a Cloudflare WARP connector.");
7021
+ if (connectivity.private !== undefined) {
7022
+ const privateNetwork = declaredPrivate;
7023
+ if (privateNetwork.mode === "cloudflare-warp") {
7024
+ exact3(privateNetwork, ["mode", "credential", "network"], `deployment.spec.targets.${targetName}.connectivity.private`);
7025
+ if (!credentials[named(privateNetwork.credential, `deployment.spec.targets.${targetName}.connectivity.private.credential`)])
7026
+ fail(`deployment.spec.targets.${targetName}.connectivity.private.credential`, "must name a declared credential.");
7027
+ if (typeof privateNetwork.network === "string")
7028
+ named(privateNetwork.network, `deployment.spec.targets.${targetName}.connectivity.private.network`);
7029
+ else
7030
+ reference(privateNetwork.network, `deployment.spec.targets.${targetName}.connectivity.private.network`);
7031
+ } else {
7032
+ exact3(privateNetwork, ["mode"], `deployment.spec.targets.${targetName}.connectivity.private`);
7033
+ if (!["disabled", "private-lan"].includes(String(privateNetwork.mode)))
7034
+ fail(`deployment.spec.targets.${targetName}.connectivity.private.mode`, "is unsupported.");
7035
+ }
7036
+ }
7037
+ if (connectivity.public !== undefined) {
7038
+ const publicNetwork = object(connectivity.public, `deployment.spec.targets.${targetName}.connectivity.public`);
7039
+ if (publicNetwork.mode === "cloudflare-tunnel") {
7040
+ exact3(publicNetwork, ["mode", "credential", "zone", "hostname"], `deployment.spec.targets.${targetName}.connectivity.public`);
7041
+ if (!credentials[named(publicNetwork.credential, `deployment.spec.targets.${targetName}.connectivity.public.credential`)])
7042
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.credential`, "must name a declared credential.");
7043
+ if (typeof publicNetwork.zone === "string") {
7044
+ if (!/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(publicNetwork.zone))
7045
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.zone`, "is invalid.");
7046
+ } else
7047
+ reference(publicNetwork.zone, `deployment.spec.targets.${targetName}.connectivity.public.zone`);
7048
+ const hostname = object(publicNetwork.hostname, `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
7049
+ const value2 = (coordinate, where, pattern) => {
7050
+ if (typeof coordinate === "string") {
7051
+ if (!pattern.test(coordinate))
7052
+ fail(where, "is invalid.");
7053
+ } else
7054
+ reference(coordinate, where);
7055
+ };
7056
+ if (hostname.mode === "static") {
7057
+ exact3(hostname, ["mode", "label"], `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
7058
+ value2(hostname.label, `deployment.spec.targets.${targetName}.connectivity.public.hostname.label`, /^[a-z0-9](?:[a-z0-9-]{0,48}[a-z0-9])?$/);
7059
+ } else if (hostname.mode === "indexed") {
7060
+ exact3(hostname, ["mode", "prefix", "startAt"], `deployment.spec.targets.${targetName}.connectivity.public.hostname`);
7061
+ value2(hostname.prefix, `deployment.spec.targets.${targetName}.connectivity.public.hostname.prefix`, /^[a-z0-9](?:[a-z0-9-]{0,48}[a-z0-9])?$/);
7062
+ if (hostname.startAt !== undefined)
7063
+ integer(hostname.startAt, `deployment.spec.targets.${targetName}.connectivity.public.hostname.startAt`, 0, 1e6);
7064
+ } else
7065
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.hostname.mode`, "is unsupported.");
7066
+ } else {
7067
+ exact3(publicNetwork, ["mode"], `deployment.spec.targets.${targetName}.connectivity.public`);
7068
+ if (publicNetwork.mode !== "disabled")
7069
+ fail(`deployment.spec.targets.${targetName}.connectivity.public.mode`, "is unsupported.");
7070
+ }
7071
+ }
7072
+ }
6725
7073
  if (target.placement !== undefined)
6726
7074
  jsonValue(target.placement, `deployment.spec.targets.${targetName}.placement`);
7075
+ targetDefinitions.set(targetName, target);
6727
7076
  }
6728
7077
  const requirements = object(spec.requirements ?? {}, "deployment.spec.requirements");
6729
7078
  if (Object.keys(requirements).length > 64)
@@ -6737,13 +7086,34 @@ function compileDeployment(sourceValue) {
6737
7086
  const key = `${requirementValue.provider}@${requirementValue.contract}:${requirementValue.capability}`;
6738
7087
  requiredProviders.set(key, { provider: requirementValue.provider, contract: requirementValue.contract, capability: requirementValue.capability });
6739
7088
  }
7089
+ for (const [targetName, target] of targetDefinitions) {
7090
+ if (target.connectivity?.public?.mode === "cloudflare-tunnel" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.public-tunnel")) {
7091
+ fail(`deployment.spec.targets.${targetName}.connectivity.public`, "requires one network.public-tunnel provider requirement.");
7092
+ }
7093
+ if (target.connectivity?.private?.mode === "cloudflare-warp" && ![...requirementDefinitions.values()].some(({ capability }) => capability === "network.private")) {
7094
+ fail(`deployment.spec.targets.${targetName}.connectivity.private`, "requires one network.private provider requirement.");
7095
+ }
7096
+ }
6740
7097
  const components = object(spec.components, "deployment.spec.components");
6741
7098
  const componentEntries = Object.entries(components);
6742
7099
  if (componentEntries.length < 1 || componentEntries.length > 128)
6743
7100
  fail("deployment.spec.components", "must contain 1 to 128 components.");
6744
7101
  for (const [componentName, value] of componentEntries) {
6745
7102
  named(componentName, "deployment.spec.components key");
6746
- validateComponent(value, `deployment.spec.components.${componentName}`, targetNames, requirementDefinitions);
7103
+ validateComponent(value, `deployment.spec.components.${componentName}`, targetDefinitions, requirementDefinitions);
7104
+ }
7105
+ for (const [targetName, target] of targetDefinitions) {
7106
+ if (target.connectivity?.public?.mode !== "cloudflare-tunnel")
7107
+ continue;
7108
+ const publicApplications = componentEntries.filter(([, component2]) => {
7109
+ const candidate = component2;
7110
+ return candidate.kind === "application" && candidate.target === targetName && candidate.network?.public?.mode === "cloudflare-tunnel";
7111
+ });
7112
+ if (publicApplications.length !== 1)
7113
+ fail(`deployment.spec.targets.${targetName}.connectivity.public`, "requires exactly one application on the target with network.public cloudflare-tunnel.");
7114
+ const application = publicApplications[0][1];
7115
+ if (application.kind !== "application" || !application.network?.ingress?.stablePort)
7116
+ fail(`deployment.spec.components.${publicApplications[0][0]}.network.ingress`, "requires one stablePort for Tunnel ingress.");
6747
7117
  }
6748
7118
  const actionContext = {
6749
7119
  components,
@@ -6785,6 +7155,14 @@ function compileDeployment(sourceValue) {
6785
7155
  });
6786
7156
  plannedWorkflows.push({ id: workflowName, ...workflowValue.concurrency ? { concurrency: workflowValue.concurrency } : {}, stages: plannedStages });
6787
7157
  }
7158
+ const deployWorkflow = plannedWorkflows.find(({ id: id2 }) => id2 === "deploy");
7159
+ for (const [targetName, target] of targetDefinitions) {
7160
+ if (!target.connectivity || target.connectivity.public?.mode !== "cloudflare-tunnel" && target.connectivity.private?.mode !== "cloudflare-warp")
7161
+ continue;
7162
+ const ensures = deployWorkflow?.stages.flatMap(({ steps }) => steps).filter((step2) => step2.uses === "forgezero.network/ensure@1" && step2.with.target === targetName && (step2.scope.kind === "each-target" || step2.scope.kind === "target-batches") && step2.scope.target === targetName) ?? [];
7163
+ if (ensures.length !== 1)
7164
+ fail(`deployment.spec.workflows.deploy`, `must contain exactly one network.ensure step scoped to connected target ${targetName}.`);
7165
+ }
6788
7166
  const typedSource = source;
6789
7167
  return {
6790
7168
  format: DEPLOY_PLAN_FORMAT,
@@ -6871,6 +7249,143 @@ function parseDeploymentPlan(value) {
6871
7249
  return { ...compiled, sourceDigest: row2.sourceDigest };
6872
7250
  }
6873
7251
 
7252
+ // src/deployment-targets.ts
7253
+ class DeploymentInputError extends Error {
7254
+ constructor(message) {
7255
+ super(message);
7256
+ this.name = "DeploymentInputError";
7257
+ }
7258
+ }
7259
+ var HOSTNAME = /^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/;
7260
+ function inputValue(name, definition, supplied) {
7261
+ let value = supplied ?? definition.default;
7262
+ if (value === undefined) {
7263
+ if (definition.required)
7264
+ throw new DeploymentInputError(`deployment input ${name} is required`);
7265
+ return;
7266
+ }
7267
+ if (typeof value === "string") {
7268
+ const text2 = value;
7269
+ if (definition.type === "boolean" && (text2 === "true" || text2 === "false"))
7270
+ value = text2 === "true";
7271
+ if ((definition.type === "integer" || definition.type === "number") && text2.trim() !== "")
7272
+ value = Number(text2);
7273
+ }
7274
+ if (definition.type === "boolean" && typeof value !== "boolean")
7275
+ throw new DeploymentInputError(`deployment input ${name} must be boolean`);
7276
+ if (definition.type === "integer" && (!Number.isSafeInteger(value) || Number(value) < (definition.minimum ?? Number.MIN_SAFE_INTEGER) || Number(value) > (definition.maximum ?? Number.MAX_SAFE_INTEGER)))
7277
+ throw new DeploymentInputError(`deployment input ${name} must be a bounded integer`);
7278
+ if (definition.type === "number" && (typeof value !== "number" || !Number.isFinite(value) || value < (definition.minimum ?? -Number.MAX_VALUE) || value > (definition.maximum ?? Number.MAX_VALUE)))
7279
+ throw new DeploymentInputError(`deployment input ${name} must be a bounded number`);
7280
+ if (definition.type === "enum" && (typeof value !== "string" || !definition.values.includes(value)))
7281
+ throw new DeploymentInputError(`deployment input ${name} must be one of its declared values`);
7282
+ if (definition.type === "hostname" && (typeof value !== "string" || !HOSTNAME.test(value)))
7283
+ throw new DeploymentInputError(`deployment input ${name} must be a hostname`);
7284
+ if (definition.type === "string") {
7285
+ if (typeof value !== "string" || value.length < (definition.minimumLength ?? 0) || value.length > (definition.maximumLength ?? 4096))
7286
+ throw new DeploymentInputError(`deployment input ${name} must be a bounded string`);
7287
+ if (definition.pattern !== undefined && !new RegExp(definition.pattern).test(value))
7288
+ throw new DeploymentInputError(`deployment input ${name} does not match its pattern`);
7289
+ }
7290
+ return value;
7291
+ }
7292
+ function resolveDeploymentTargets(plan, supplied = {}) {
7293
+ const inputs = resolveDeploymentInputs(plan, supplied);
7294
+ const stringCoordinate = (value, label) => {
7295
+ const resolved = typeof value === "string" ? value : value.$ref.startsWith("inputs.") ? inputs[value.$ref.slice("inputs.".length)] : undefined;
7296
+ if (typeof resolved !== "string" || resolved.length < 1 || resolved.length > 253) {
7297
+ throw new DeploymentInputError(`${label} did not resolve to a bounded string`);
7298
+ }
7299
+ return resolved.toLowerCase();
7300
+ };
7301
+ const integerCoordinate = (value, label, minimum, maximum) => {
7302
+ const resolved = typeof value === "number" ? value : value.$ref.startsWith("inputs.") ? inputs[value.$ref.slice("inputs.".length)] : undefined;
7303
+ if (!Number.isSafeInteger(resolved) || Number(resolved) < minimum || Number(resolved) > maximum) {
7304
+ throw new DeploymentInputError(`${label} did not resolve to a bounded integer`);
7305
+ }
7306
+ return Number(resolved);
7307
+ };
7308
+ const credentialSource = (alias, label) => {
7309
+ const source = plan.spec.credentials?.[alias]?.source.name;
7310
+ if (!source)
7311
+ throw new DeploymentInputError(`${label} did not resolve to a declared Vault credential`);
7312
+ return source;
7313
+ };
7314
+ return Object.entries(plan.spec.targets).map(([name, target]) => {
7315
+ const desired = typeof target.cardinality.desired === "number" ? target.cardinality.desired : target.cardinality.desired.$ref.startsWith("inputs.") ? inputs[target.cardinality.desired.$ref.slice("inputs.".length)] : undefined;
7316
+ if (!Number.isSafeInteger(desired) || Number(desired) < target.cardinality.minimum || Number(desired) > target.cardinality.maximum) {
7317
+ throw new DeploymentInputError(`target ${name} desired cardinality did not resolve to a bounded integer`);
7318
+ }
7319
+ let connectivity;
7320
+ if (target.connectivity) {
7321
+ const privateNetwork = target.connectivity.private;
7322
+ const publicNetwork = target.connectivity.public;
7323
+ connectivity = {
7324
+ ...privateNetwork ? { private: privateNetwork.mode === "cloudflare-warp" ? {
7325
+ mode: "cloudflare-warp",
7326
+ credential: credentialSource(privateNetwork.credential, `target ${name} WARP credential`),
7327
+ network: stringCoordinate(privateNetwork.network, `target ${name} WARP network`)
7328
+ } : { mode: privateNetwork.mode } } : {},
7329
+ ...publicNetwork ? { public: publicNetwork.mode === "cloudflare-tunnel" ? {
7330
+ mode: "cloudflare-tunnel",
7331
+ credential: credentialSource(publicNetwork.credential, `target ${name} Tunnel credential`),
7332
+ zone: stringCoordinate(publicNetwork.zone, `target ${name} Tunnel zone`),
7333
+ hostname: publicNetwork.hostname.mode === "static" ? {
7334
+ mode: "static",
7335
+ label: stringCoordinate(publicNetwork.hostname.label, `target ${name} Tunnel label`)
7336
+ } : {
7337
+ mode: "indexed",
7338
+ prefix: stringCoordinate(publicNetwork.hostname.prefix, `target ${name} Tunnel prefix`),
7339
+ startAt: publicNetwork.hostname.startAt ?? 1
7340
+ },
7341
+ service: (() => {
7342
+ const applications = Object.values(plan.spec.components).filter((component2) => component2.target === name && component2.kind === "application" && component2.network?.public?.mode === "cloudflare-tunnel");
7343
+ if (applications.length !== 1)
7344
+ throw new DeploymentInputError(`target ${name} Tunnel requires exactly one public application`);
7345
+ const port = applications[0].network.ingress?.stablePort;
7346
+ if (!Number.isInteger(port))
7347
+ throw new DeploymentInputError(`target ${name} Tunnel application requires a stable ingress port`);
7348
+ return { protocol: "http", port: Number(port) };
7349
+ })()
7350
+ } : { mode: "disabled" } } : {}
7351
+ };
7352
+ }
7353
+ return {
7354
+ name,
7355
+ profiles: [...target.selector.profiles],
7356
+ operatingSystem: { ...target.selector.operatingSystem },
7357
+ confidentialCompute: target.selector.confidentialCompute ?? "disabled",
7358
+ labels: { ...target.selector.labels ?? {} },
7359
+ minimum: target.cardinality.minimum,
7360
+ desired: Number(desired),
7361
+ maximum: target.cardinality.maximum,
7362
+ allocation: {
7363
+ ...target.allocation,
7364
+ resources: {
7365
+ cpuCores: integerCoordinate(target.allocation.resources.cpuCores, `target ${name} cpuCores`, 1, 1024),
7366
+ memoryMiB: integerCoordinate(target.allocation.resources.memoryMiB, `target ${name} memoryMiB`, 128, 4194304),
7367
+ storageGiB: integerCoordinate(target.allocation.resources.storageGiB, `target ${name} storageGiB`, 1, 1048576)
7368
+ }
7369
+ },
7370
+ ...target.provisioning ? { provisioning: { ...target.provisioning } } : {},
7371
+ ...connectivity ? { connectivity } : {}
7372
+ };
7373
+ });
7374
+ }
7375
+ function resolveDeploymentInputs(plan, supplied = {}) {
7376
+ for (const name of Object.keys(supplied))
7377
+ if (!(name in (plan.spec.inputs ?? {}))) {
7378
+ throw new DeploymentInputError(`deployment input ${name} is not declared`);
7379
+ }
7380
+ const inputs = {};
7381
+ for (const [name, definition] of Object.entries(plan.spec.inputs ?? {})) {
7382
+ const value = inputValue(name, definition, supplied[name]);
7383
+ if (value !== undefined)
7384
+ inputs[name] = value;
7385
+ }
7386
+ return inputs;
7387
+ }
7388
+
6874
7389
  // src/deploy-plan-runner.ts
6875
7390
  class DeploymentActionError extends Error {
6876
7391
  kind;
@@ -6906,6 +7421,8 @@ function resolveReference(reference2, state) {
6906
7421
  return nested(state.plan.spec.targets[parts[1]], parts.slice(2));
6907
7422
  if (parts[0] === "deployment")
6908
7423
  return nested(state.plan, parts.slice(1));
7424
+ if (parts[0] === "execution")
7425
+ return nested(state.execution, parts.slice(1));
6909
7426
  return;
6910
7427
  }
6911
7428
  function resolveOperand(value, state) {
@@ -7108,7 +7625,7 @@ async function runDeploymentPlan(options) {
7108
7625
  for (const [name, definition] of Object.entries(inputDefinitions))
7109
7626
  inputValues[name] = validateInputValue(definition, supplied[name], name);
7110
7627
  const execute = async () => {
7111
- const state = { inputs: inputValues, steps: new Map, plan };
7628
+ const state = { inputs: inputValues, steps: new Map, plan, execution: options.execution };
7112
7629
  const records = [];
7113
7630
  const successful2 = [];
7114
7631
  for (const stage2 of workflow.stages) {
@@ -7163,7 +7680,7 @@ var DEPLOY_PLAN_FILE = ".fz/deploy.plan.json";
7163
7680
  import { createHash as createHash4, randomBytes as randomBytes5 } from "crypto";
7164
7681
  import {
7165
7682
  chmodSync as chmodSync3,
7166
- createReadStream,
7683
+ createReadStream as createReadStream2,
7167
7684
  existsSync as existsSync3,
7168
7685
  lstatSync,
7169
7686
  mkdirSync as mkdirSync2,
@@ -7178,7 +7695,7 @@ var MAX_BOOTSTRAP_BUNDLE_BYTES = 512 * 1024 * 1024;
7178
7695
  async function sha256File(path2) {
7179
7696
  const hash = createHash4("sha256");
7180
7697
  await new Promise((resolveDone, reject) => {
7181
- const stream = createReadStream(path2);
7698
+ const stream = createReadStream2(path2);
7182
7699
  stream.on("data", (chunk) => hash.update(chunk));
7183
7700
  stream.on("error", reject);
7184
7701
  stream.on("end", resolveDone);
@@ -7264,7 +7781,7 @@ function persistCapacityCalibration(args) {
7264
7781
  throw new DeploymentError("PIPELINE_FAILED", "capacity evidence directory must be an absolute Agent-owned path");
7265
7782
  }
7266
7783
  if (!existsSync4(args.directory)) {
7267
- const parent = lstatSync2(dirname(args.directory));
7784
+ const parent = lstatSync2(dirname2(args.directory));
7268
7785
  if (!parent.isDirectory() || parent.isSymbolicLink()) {
7269
7786
  throw new DeploymentError("PIPELINE_FAILED", "capacity evidence parent must be a real Agent-owned directory");
7270
7787
  }
@@ -7328,7 +7845,7 @@ function createDeploymentManager(options) {
7328
7845
  }
7329
7846
  if (existsSync4(knownHostsPath) && readFileSync3(knownHostsPath, "utf8") === content)
7330
7847
  return;
7331
- mkdirSync3(dirname(knownHostsPath), { recursive: true, mode: 448 });
7848
+ mkdirSync3(dirname2(knownHostsPath), { recursive: true, mode: 448 });
7332
7849
  const next = `${knownHostsPath}.${process.pid}.${randomUUID()}.next`;
7333
7850
  writeFileSync3(next, content, { mode: 384, flag: "wx" });
7334
7851
  renameSync3(next, knownHostsPath);
@@ -7515,8 +8032,8 @@ function createDeploymentManager(options) {
7515
8032
  throw new DeploymentError("PIPELINE_FAILED", "rolling/canary plans require a fenced multi-target rollout lease that this control protocol does not yet supply");
7516
8033
  }
7517
8034
  }
7518
- const targetNames = new Set(Object.entries(plan.spec.targets).filter(([, target]) => target.selector.profiles.includes(options.profile)).map(([name]) => name));
7519
- if (targetNames.size === 0)
8035
+ const matchingTargetNames = Object.entries(plan.spec.targets).filter(([, target]) => target.selector.profiles.includes(options.profile)).map(([name]) => name);
8036
+ if (matchingTargetNames.length === 0)
7520
8037
  throw new DeploymentError("PIPELINE_FAILED", `deployment plan has no target for profile ${options.profile}`);
7521
8038
  const assurance = plan.spec.security?.attestation ?? "disabled";
7522
8039
  if (assurance !== "disabled") {
@@ -7539,35 +8056,66 @@ function createDeploymentManager(options) {
7539
8056
  FZ_DEPLOY_PROFILE: options.profile,
7540
8057
  ...options.publicApiUrl ? { PUBLIC_API_URL: options.publicApiUrl } : {}
7541
8058
  };
7542
- const inputValues = {};
7543
- for (const [name, definition2] of Object.entries(plan.spec.inputs ?? {})) {
7544
- const raw = options.environment?.[name];
7545
- if (raw === undefined) {
7546
- if (definition2.default !== undefined)
7547
- inputValues[name] = definition2.default;
7548
- continue;
8059
+ const resolvedTargets = resolveDeploymentTargets(plan, options.environment ?? {}).filter((target) => target.profiles.includes(options.profile));
8060
+ const assigned = request.assignment;
8061
+ let targetNames;
8062
+ if (assigned) {
8063
+ const selected = resolvedTargets.find(({ name }) => name === assigned.targetName);
8064
+ const mismatches = [
8065
+ !selected ? "target" : null,
8066
+ assigned.definitionDigest !== plan.sourceDigest ? "digest" : null,
8067
+ !Number.isSafeInteger(assigned.slot) || assigned.slot < 0 || selected && assigned.slot >= selected.maximum ? "slot" : null,
8068
+ selected && (assigned.operatingSystem.id !== selected.operatingSystem.id || assigned.operatingSystem.version !== selected.operatingSystem.version || assigned.operatingSystem.architecture !== selected.operatingSystem.architecture) ? "operating-system" : null,
8069
+ selected && assigned.confidentialCompute !== selected.confidentialCompute ? "confidential-compute" : null,
8070
+ selected && assigned.execution !== selected.allocation.execution ? "execution" : null,
8071
+ selected && assigned.sharing !== selected.allocation.sharing ? "sharing" : null,
8072
+ selected && (assigned.resources.cpuCores !== selected.allocation.resources.cpuCores || assigned.resources.memoryMiB !== selected.allocation.resources.memoryMiB || assigned.resources.storageGiB !== selected.allocation.resources.storageGiB) ? "resources" : null
8073
+ ].filter(Boolean);
8074
+ if (!selected || mismatches.length > 0) {
8075
+ throw new DeploymentError("PIPELINE_FAILED", `the control-plane target assignment does not match this exact deployment plan (${mismatches.join(", ")})`);
8076
+ }
8077
+ const expectedPrivate = selected.connectivity?.private;
8078
+ const expectedPublic = selected.connectivity?.public;
8079
+ const assignedPrivate = assigned.connectivity?.private;
8080
+ const assignedPublic = assigned.connectivity?.public;
8081
+ const publicMatches = expectedPublic?.mode === "cloudflare-tunnel" ? assignedPublic?.mode === "cloudflare-tunnel" && assignedPublic.credential === expectedPublic.credential && assignedPublic.service.protocol === expectedPublic.service.protocol && assignedPublic.service.port === expectedPublic.service.port && assignedPublic.hostname === (expectedPublic.hostname.mode === "static" ? `${expectedPublic.hostname.label}.${expectedPublic.zone}` : `${expectedPublic.hostname.prefix}${expectedPublic.hostname.startAt + assigned.slot}.${expectedPublic.zone}`) : expectedPublic?.mode === "disabled" ? assignedPublic?.mode === "disabled" : assignedPublic === undefined;
8082
+ if (JSON.stringify(assignedPrivate) !== JSON.stringify(expectedPrivate) || !publicMatches) {
8083
+ throw new DeploymentError("PIPELINE_FAILED", "the control-plane connectivity assignment does not match this target slot");
8084
+ }
8085
+ targetNames = new Set([assigned.targetName]);
8086
+ } else {
8087
+ if (matchingTargetNames.length !== 1) {
8088
+ throw new DeploymentError("PIPELINE_FAILED", "an unassigned bootstrap claim must resolve to exactly one target for its profile");
7549
8089
  }
7550
- inputValues[name] = definition2.type === "boolean" ? raw === "true" : definition2.type === "integer" || definition2.type === "number" ? Number(raw) : raw;
8090
+ targetNames = new Set(matchingTargetNames);
7551
8091
  }
7552
- const resolvedTargets = Object.entries(plan.spec.targets).filter(([, target]) => target.selector.profiles.includes(options.profile)).map(([name, target]) => {
7553
- const desired = typeof target.cardinality.desired === "number" ? target.cardinality.desired : target.cardinality.desired.$ref.startsWith("inputs.") ? inputValues[target.cardinality.desired.$ref.slice("inputs.".length)] : undefined;
7554
- if (!Number.isSafeInteger(desired) || Number(desired) < target.cardinality.minimum || Number(desired) > target.cardinality.maximum) {
7555
- throw new DeploymentError("PIPELINE_FAILED", `target ${name} desired cardinality did not resolve to a bounded integer`);
8092
+ if (assigned)
8093
+ Object.assign(phaseEnvironment2, {
8094
+ FZ_TARGET_NAME: assigned.targetName,
8095
+ FZ_TARGET_SLOT: String(assigned.slot),
8096
+ FZ_TARGET_CPU_CORES: String(assigned.resources.cpuCores),
8097
+ FZ_TARGET_MEMORY_MIB: String(assigned.resources.memoryMiB),
8098
+ FZ_TARGET_STORAGE_GIB: String(assigned.resources.storageGiB),
8099
+ FZ_TARGET_EXECUTION: assigned.execution
8100
+ });
8101
+ for (const targetName of targetNames) {
8102
+ const capacity2 = resolvedTargets.find(({ name }) => name === targetName)?.allocation.resources;
8103
+ if (!capacity2)
8104
+ throw new DeploymentError("PIPELINE_FAILED", `target ${targetName} has no resolved capacity`);
8105
+ const components = Object.values(plan.spec.components).filter(({ target }) => target === targetName);
8106
+ const cpu = components.reduce((sum, component2) => sum + (component2.resources?.cpu?.limit ?? 0), 0);
8107
+ const memoryMiB = components.reduce((sum, component2) => sum + (component2.resources?.memory?.limitMiB ?? 0), 0);
8108
+ const ephemeralMiB = components.reduce((sum, component2) => sum + (component2.kind === "application" ? (component2.storage ?? []).reduce((storage, mount) => storage + (mount.class === "ephemeral" ? mount.sizeMiB : 0), 0) : 0), 0);
8109
+ if (cpu > capacity2.cpuCores || memoryMiB > capacity2.memoryMiB || ephemeralMiB > capacity2.storageGiB * 1024) {
8110
+ throw new DeploymentError("PIPELINE_FAILED", `component limits exceed the reserved capacity of target ${targetName}`);
7556
8111
  }
7557
- return {
7558
- name,
7559
- profiles: [...target.selector.profiles],
7560
- confidentialCompute: target.selector.confidentialCompute ?? "disabled",
7561
- labels: { ...target.selector.labels ?? {} },
7562
- minimum: target.cardinality.minimum,
7563
- desired: Number(desired),
7564
- maximum: target.cardinality.maximum
7565
- };
7566
- });
8112
+ }
8113
+ const inputValues = resolveDeploymentInputs(plan, options.environment ?? {});
7567
8114
  const projectExec2 = options.projectExec ?? spawnExact;
7568
8115
  const materializePlanArgv = (argv2) => argv2.map((argument) => {
7569
- if (argument === "${FZ_RELEASE}")
7570
- return release;
8116
+ const coordinate = argument.match(/^\$\{(FZ_[A-Z0-9_]+)\}$/)?.[1];
8117
+ if (coordinate && phaseEnvironment2[coordinate] !== undefined)
8118
+ return phaseEnvironment2[coordinate];
7571
8119
  if (argument.includes("${")) {
7572
8120
  throw new DeploymentActionError("refused", `unsupported deployment argv coordinate ${argument}`);
7573
8121
  }
@@ -7576,6 +8124,8 @@ function createDeploymentManager(options) {
7576
8124
  const providerSoftware = {
7577
8125
  "forgezero.bun": "bun",
7578
8126
  "forgezero.docker": "docker",
8127
+ "forgezero.containerd": "containerd",
8128
+ "forgezero.kata": "kata-containers",
7579
8129
  "forgezero.nginx": "nginx",
7580
8130
  "forgezero.arangodb": "arangodb",
7581
8131
  "forgezero.cloudflared": "cloudflared",
@@ -7601,6 +8151,29 @@ function createDeploymentManager(options) {
7601
8151
  await options.ensureSoftware(requirements);
7602
8152
  return { changed: true };
7603
8153
  },
8154
+ "forgezero.network/ensure@1": async ({ resolved }) => {
8155
+ if (!assigned || typeof resolved.target !== "string" || resolved.target !== assigned.targetName) {
8156
+ throw new DeploymentActionError("refused", "network.ensure must target this exact control-plane assignment");
8157
+ }
8158
+ if (!assigned.connectivity)
8159
+ return { changed: false, evidence: { target: assigned.targetName } };
8160
+ if (!options.ensureSoftware || !options.applyConnectivity) {
8161
+ throw new DeploymentActionError("provider-unavailable", "the supervised connectivity helper is unavailable");
8162
+ }
8163
+ const requirements = [];
8164
+ if (assigned.connectivity.public?.mode === "cloudflare-tunnel")
8165
+ requirements.push({ id: "cloudflared", version: "latest" });
8166
+ if (assigned.connectivity.private?.mode === "cloudflare-warp")
8167
+ requirements.push({ id: "cloudflare-warp", version: "latest" });
8168
+ if (requirements.length > 0)
8169
+ await options.ensureSoftware(requirements);
8170
+ const evidence = await options.applyConnectivity({
8171
+ key: `${options.key}:${assigned.targetName}:${assigned.slot}`,
8172
+ intent: assigned.connectivity,
8173
+ capabilities: request.connectivityCapabilities ?? {}
8174
+ });
8175
+ return { changed: true, evidence: structuredClone(evidence) };
8176
+ },
7604
8177
  "forgezero.exec/argv@1": async ({ resolved, step: step2 }) => {
7605
8178
  if (!Array.isArray(resolved.argv) || resolved.argv.some((value) => typeof value !== "string"))
7606
8179
  throw new DeploymentActionError("refused", "exec.argv requires an exact argv array");
@@ -7642,7 +8215,7 @@ function createDeploymentManager(options) {
7642
8215
  const component2 = plan.spec.components[String(resolved.component ?? "")];
7643
8216
  const mounts = component2?.kind === "database" ? [component2.storage] : component2?.storage ?? [];
7644
8217
  for (const mount of mounts) {
7645
- const candidate = existsSync4(mount.path) ? mount.path : dirname(mount.path);
8218
+ const candidate = existsSync4(mount.path) ? mount.path : dirname2(mount.path);
7646
8219
  const stats = statfsSync(candidate);
7647
8220
  const freePercent = Number(stats.bavail) / Number(stats.blocks) * 100;
7648
8221
  const minimum = mount.class === "ephemeral" ? 0 : mount.minimumFreePercent ?? 0;
@@ -7699,6 +8272,23 @@ function createDeploymentManager(options) {
7699
8272
  workflow: "deploy",
7700
8273
  inputs: inputValues,
7701
8274
  actions: actions2,
8275
+ execution: assigned ? {
8276
+ targetName: assigned.targetName,
8277
+ slot: assigned.slot,
8278
+ releaseExecutor: request.releaseExecutor === true,
8279
+ runtime: assigned.execution,
8280
+ cpuCores: assigned.resources.cpuCores,
8281
+ memoryMiB: assigned.resources.memoryMiB,
8282
+ storageGiB: assigned.resources.storageGiB
8283
+ } : {
8284
+ targetName: matchingTargetNames[0],
8285
+ slot: 0,
8286
+ releaseExecutor: request.releaseExecutor === true,
8287
+ runtime: resolvedTargets[0].allocation.execution,
8288
+ cpuCores: resolvedTargets[0].allocation.resources.cpuCores,
8289
+ memoryMiB: resolvedTargets[0].allocation.resources.memoryMiB,
8290
+ storageGiB: resolvedTargets[0].allocation.resources.storageGiB
8291
+ },
7702
8292
  shouldRun: (step2) => step2.scope.kind === "release-executor" || step2.scope.kind === "elected-one" ? request.releaseExecutor === true : targetNames.has(step2.scope.target)
7703
8293
  });
7704
8294
  const phase = {
@@ -8060,7 +8650,12 @@ async function deployClaim(options, claim) {
8060
8650
  schedule();
8061
8651
  let result;
8062
8652
  try {
8063
- const run2 = () => manager.deploy({ revision: claim.revision, releaseExecutor: claim.releaseExecutor }).result;
8653
+ const run2 = () => manager.deploy({
8654
+ revision: claim.revision,
8655
+ releaseExecutor: claim.releaseExecutor,
8656
+ ...claim.assignment ? { assignment: claim.assignment } : {},
8657
+ ...claim.connectivityCapabilities ? { connectivityCapabilities: claim.connectivityCapabilities } : {}
8658
+ }).result;
8064
8659
  result = options.telemetry ? await options.telemetry.observe("deployment.run", run2) : await run2();
8065
8660
  } finally {
8066
8661
  stopped = true;
@@ -8130,7 +8725,11 @@ async function pullDeploymentOnce(options) {
8130
8725
  ...result.resolvedTargets ? { resolvedTargets: result.resolvedTargets.map((target) => ({
8131
8726
  ...target,
8132
8727
  profiles: [...target.profiles],
8133
- labels: { ...target.labels }
8728
+ operatingSystem: { ...target.operatingSystem },
8729
+ labels: { ...target.labels },
8730
+ allocation: { ...target.allocation, resources: { ...target.allocation.resources } },
8731
+ ...target.provisioning ? { provisioning: { ...target.provisioning } } : {},
8732
+ ...target.connectivity ? { connectivity: structuredClone(target.connectivity) } : {}
8134
8733
  })) } : {},
8135
8734
  ...result.capacity ? { capacity: {
8136
8735
  recommendedConcurrency: result.capacity.recommendedConcurrency,
@@ -8186,11 +8785,11 @@ import {
8186
8785
  mkdirSync as mkdirSync4,
8187
8786
  readFileSync as readFileSync4,
8188
8787
  renameSync as renameSync4,
8189
- statSync,
8788
+ statSync as statSync2,
8190
8789
  unlinkSync as unlinkSync4,
8191
8790
  writeFileSync as writeFileSync4
8192
8791
  } from "fs";
8193
- import { dirname as dirname2 } from "path";
8792
+ import { dirname as dirname3 } from "path";
8194
8793
  function privateNetworkAttachmentFromEnvironment(env = process.env) {
8195
8794
  const values = {
8196
8795
  accountId: env.FZ_CF_ACCOUNT_ID?.trim() ?? "",
@@ -8220,7 +8819,7 @@ var validBinding = (value, expectedNodeKey) => {
8220
8819
  function loadGuestBinding(path2, expectedNodeKey) {
8221
8820
  if (!existsSync6(path2))
8222
8821
  return null;
8223
- const mode = statSync(path2).mode & 511;
8822
+ const mode = statSync2(path2).mode & 511;
8224
8823
  if ((mode & 63) !== 0) {
8225
8824
  throw new Error(`guest enrolment state at ${path2} is not private`);
8226
8825
  }
@@ -8236,7 +8835,7 @@ function loadGuestBinding(path2, expectedNodeKey) {
8236
8835
  return parsed;
8237
8836
  }
8238
8837
  function persistGuestBinding(path2, binding) {
8239
- mkdirSync4(dirname2(path2), { recursive: true, mode: 448 });
8838
+ mkdirSync4(dirname3(path2), { recursive: true, mode: 448 });
8240
8839
  const temporary = `${path2}.next`;
8241
8840
  writeFileSync4(temporary, `${JSON.stringify(binding)}
8242
8841
  `, { mode: 384 });
@@ -8588,7 +9187,7 @@ import { join as join3 } from "path";
8588
9187
  import { fileURLToPath } from "url";
8589
9188
 
8590
9189
  // src/version.ts
8591
- var VERSION2 = "0.1.74";
9190
+ var VERSION2 = "0.1.75";
8592
9191
 
8593
9192
  // src/ssh-bootstrap.ts
8594
9193
  class SshBootstrapError extends Error {
@@ -9304,11 +9903,11 @@ import {
9304
9903
  readFileSync as readFileSync6,
9305
9904
  readdirSync,
9306
9905
  rmSync as rmSync4,
9307
- statSync as statSync2,
9906
+ statSync as statSync3,
9308
9907
  unlinkSync as unlinkSync5,
9309
9908
  writeFileSync as writeFileSync6
9310
9909
  } from "fs";
9311
- import { dirname as dirname3, isAbsolute as isAbsolute3, join as join4 } from "path";
9910
+ import { dirname as dirname4, isAbsolute as isAbsolute3, join as join4 } from "path";
9312
9911
  import { isIP as isIP2 } from "net";
9313
9912
 
9314
9913
  // src/compute.ts
@@ -9733,7 +10332,7 @@ async function provisionMetalGuest(profile, claim, exec) {
9733
10332
  if (!image || !isAbsolute3(image.path) || !SHA256.test(image.sha256)) {
9734
10333
  throw new MetalProvisionError(`image ${claim.spec.imageKey} is not configured locally`);
9735
10334
  }
9736
- if (!statSync2(image.path).isFile())
10335
+ if (!statSync3(image.path).isFile())
9737
10336
  throw new MetalProvisionError("configured image is not a file");
9738
10337
  const digest = (await checked2(exec, ["sha256sum", image.path])).stdout.trim().split(/\s+/)[0];
9739
10338
  if (digest !== image.sha256)
@@ -9818,7 +10417,7 @@ async function provisionMetalGuest(profile, claim, exec) {
9818
10417
  }
9819
10418
  const service = `forgezero-guest@${name}.service`;
9820
10419
  const unitPath = join4(profile.unitDir, service);
9821
- mkdirSync5(dirname3(unitPath), { recursive: true });
10420
+ mkdirSync5(dirname4(unitPath), { recursive: true });
9822
10421
  writeFileSync6(unitPath, guestUnit(spec), { mode: 420 });
9823
10422
  await checked2(exec, ["systemctl", "daemon-reload"]);
9824
10423
  if (prior?.phase === "running") {
@@ -10882,7 +11481,7 @@ import {
10882
11481
  symlinkSync as symlinkSync3,
10883
11482
  writeFileSync as writeFileSync9
10884
11483
  } from "fs";
10885
- import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
11484
+ import { dirname as dirname5, join as join7, resolve as resolve2 } from "path";
10886
11485
  var DEFAULT_AGENT_RELEASE_ROOT = "/opt/forgezero/agent";
10887
11486
  var DEFAULT_AGENT_UPDATE_SOCKET = "/run/forgezero-update/helper.sock";
10888
11487
  var MAX_AGENT_TARBALL_BYTES = 32 * 1024 * 1024;
@@ -10904,7 +11503,7 @@ var syncReleaseDirectory = (directory) => {
10904
11503
  join7(directory, "dist", "fz-git-ssh.js"),
10905
11504
  join7(directory, "dist"),
10906
11505
  directory,
10907
- dirname4(directory)
11506
+ dirname5(directory)
10908
11507
  ])
10909
11508
  syncPath(path2);
10910
11509
  };
@@ -11026,7 +11625,7 @@ async function stageAgentRelease(releaseInput, options) {
11026
11625
  args: ["-xOzf", archive, member]
11027
11626
  }, `agent update extraction of ${member}`);
11028
11627
  const destination = join7(unpacked, relative);
11029
- mkdirSync8(dirname4(destination), { recursive: true, mode: 448 });
11628
+ mkdirSync8(dirname5(destination), { recursive: true, mode: 448 });
11030
11629
  writeFileSync9(destination, extracted.output, { mode: 384, flag: "wx" });
11031
11630
  }
11032
11631
  await validateReleaseDirectory(unpacked, release, run2);
@@ -11058,21 +11657,21 @@ async function stageAgentRelease(releaseInput, options) {
11058
11657
  }
11059
11658
  }
11060
11659
  function selectAgentRelease(staged) {
11061
- const next = join7(dirname4(staged.currentLink), `.current.${randomUUID2()}.next`);
11660
+ const next = join7(dirname5(staged.currentLink), `.current.${randomUUID2()}.next`);
11062
11661
  try {
11063
11662
  symlinkSync3(staged.nextTarget, next);
11064
11663
  renameSync6(next, staged.currentLink);
11065
- syncPath(dirname4(staged.currentLink));
11664
+ syncPath(dirname5(staged.currentLink));
11066
11665
  } finally {
11067
11666
  rmSync5(next, { force: true });
11068
11667
  }
11069
11668
  }
11070
11669
  function restoreAgentRelease(staged) {
11071
- const next = join7(dirname4(staged.currentLink), `.current.${randomUUID2()}.rollback`);
11670
+ const next = join7(dirname5(staged.currentLink), `.current.${randomUUID2()}.rollback`);
11072
11671
  try {
11073
11672
  symlinkSync3(staged.previousTarget, next);
11074
11673
  renameSync6(next, staged.currentLink);
11075
- syncPath(dirname4(staged.currentLink));
11674
+ syncPath(dirname5(staged.currentLink));
11076
11675
  } finally {
11077
11676
  rmSync5(next, { force: true });
11078
11677
  }
@@ -11094,7 +11693,7 @@ import {
11094
11693
  writeFileSync as writeFileSync10
11095
11694
  } from "fs";
11096
11695
  import { connect as connect5, createServer as createServer6 } from "net";
11097
- import { dirname as dirname5, join as join8, resolve as resolve3 } from "path";
11696
+ import { dirname as dirname6, join as join8, resolve as resolve3 } from "path";
11098
11697
  var AGENT_UPDATE_GROUP = "forgezero-update";
11099
11698
  var AGENT_UPDATE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-agent-update-helper.service";
11100
11699
  var AGENT_UPDATE_JOURNAL = "/var/lib/forgezero/agent-update.json";
@@ -11194,7 +11793,7 @@ function readJournal(path2, root) {
11194
11793
  return validateJournal(JSON.parse(readFileSync10(path2, "utf8")), root);
11195
11794
  }
11196
11795
  function writeAtomic(path2, value, mode) {
11197
- mkdirSync9(dirname5(path2), { recursive: true, mode: 493 });
11796
+ mkdirSync9(dirname6(path2), { recursive: true, mode: 493 });
11198
11797
  const next = `${path2}.${randomUUID3()}.next`;
11199
11798
  let file;
11200
11799
  try {
@@ -11205,7 +11804,7 @@ function writeAtomic(path2, value, mode) {
11205
11804
  closeSync3(file);
11206
11805
  file = undefined;
11207
11806
  renameSync7(next, path2);
11208
- const directory = openSync3(dirname5(path2), "r");
11807
+ const directory = openSync3(dirname6(path2), "r");
11209
11808
  try {
11210
11809
  fsyncSync2(directory);
11211
11810
  } finally {
@@ -11327,7 +11926,7 @@ async function activateAgentRelease(staged, options = {}) {
11327
11926
  const now = options.now ?? Date.now;
11328
11927
  const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
11329
11928
  const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
11330
- const previous = readJournal(journalPath, dirname5(staged.currentLink));
11929
+ const previous = readJournal(journalPath, dirname6(staged.currentLink));
11331
11930
  const attemptId = options.attemptId ?? randomUUID3();
11332
11931
  if (!ATTEMPT_ID.test(attemptId))
11333
11932
  throw new Error("Agent update attempt ID is invalid");
@@ -11435,7 +12034,7 @@ function startAgentUpdateHelper(options = {}) {
11435
12034
  const socketPath = options.socketPath ?? DEFAULT_AGENT_UPDATE_SOCKET;
11436
12035
  if (existsSync13(socketPath))
11437
12036
  unlinkSync10(socketPath);
11438
- mkdirSync9(dirname5(socketPath), { recursive: true, mode: 488 });
12037
+ mkdirSync9(dirname6(socketPath), { recursive: true, mode: 488 });
11439
12038
  const setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
11440
12039
  const receiptPath = options.receiptPath ?? AGENT_UPDATE_RECEIPT;
11441
12040
  const journalPath = options.journalPath ?? AGENT_UPDATE_JOURNAL;
@@ -11544,23 +12143,63 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
11544
12143
  }
11545
12144
 
11546
12145
  // src/agent-heartbeat.ts
11547
- import { readFileSync as readFileSync11 } from "fs";
12146
+ import { existsSync as existsSync14, readFileSync as readFileSync11, statfsSync as statfsSync2 } from "fs";
12147
+ import { cpus, freemem, loadavg, totalmem } from "os";
12148
+ function readAgentHostMetrics() {
12149
+ const filesystem = statfsSync2("/");
12150
+ return {
12151
+ logicalCpu: cpus().length,
12152
+ memoryBytes: totalmem(),
12153
+ memoryFreeBytes: freemem(),
12154
+ storageBytes: Number(filesystem.blocks) * Number(filesystem.bsize),
12155
+ storageFreeBytes: Number(filesystem.bavail) * Number(filesystem.bsize),
12156
+ load1m: loadavg()[0]
12157
+ };
12158
+ }
11548
12159
  var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
11549
12160
  var remoteOutcome4 = (cause) => cause instanceof SignedNodeHttpError && cause.status < 500 ? "refused" : cause instanceof SignedNodeHttpError ? "retryable" : "failed";
11550
12161
 
11551
12162
  class AgentUpdateRefusedError extends Error {
11552
12163
  }
11553
- function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = readFileSync11("/etc/os-release", "utf8"), architecture = process.arch) {
12164
+ function observeAgentHost(version = VERSION2, mode = "enrolled", osRelease = readFileSync11("/etc/os-release", "utf8"), architecture = process.arch, metrics = readAgentHostMetrics()) {
11554
12165
  const values = Object.fromEntries(osRelease.split(`
11555
12166
  `).flatMap((line) => {
11556
12167
  const separator = line.indexOf("=");
11557
12168
  return separator > 0 ? [[line.slice(0, separator), unquote(line.slice(separator + 1))]] : [];
11558
12169
  }));
12170
+ const osId = (values.ID ?? "unknown").toLowerCase();
12171
+ const osVersion = values.VERSION_ID ?? "unknown";
12172
+ const ubuntuX64 = osId === "ubuntu" && ["24.04", "26.04"].includes(osVersion) && architecture === "x64";
12173
+ const enabled = (path2) => {
12174
+ try {
12175
+ return /^(?:1|y)$/i.test(readFileSync11(path2, "utf8").trim());
12176
+ } catch {
12177
+ return false;
12178
+ }
12179
+ };
12180
+ const gib = 1024 ** 3;
12181
+ const mib = 1024 ** 2;
12182
+ const storageGiB = Math.max(1, Math.floor(metrics.storageBytes / gib));
11559
12183
  return {
11560
12184
  version,
11561
- os: { id: (values.ID ?? "unknown").toLowerCase(), versionId: values.VERSION_ID ?? "unknown" },
12185
+ os: { id: osId, versionId: osVersion },
11562
12186
  architecture,
11563
- mode
12187
+ mode,
12188
+ runtimeCapabilities: {
12189
+ native: true,
12190
+ ociRunc: ubuntuX64,
12191
+ kataQemuSnp: ubuntuX64 && osVersion === "26.04" && existsSync14("/dev/kvm") && existsSync14("/dev/sev") && enabled("/sys/module/kvm_amd/parameters/sev") && enabled("/sys/module/kvm_amd/parameters/sev_snp")
12192
+ },
12193
+ capacity: {
12194
+ logicalCpu: Math.max(1, metrics.logicalCpu),
12195
+ memoryMiB: Math.max(1, Math.floor(metrics.memoryBytes / mib)),
12196
+ storageGiB
12197
+ },
12198
+ utilization: {
12199
+ load1mMilli: Math.max(0, Math.round(metrics.load1m * 1000)),
12200
+ memoryUsedMiB: Math.max(0, Math.floor((metrics.memoryBytes - metrics.memoryFreeBytes) / mib)),
12201
+ storageUsedGiB: Math.max(0, Math.min(storageGiB, Math.ceil((metrics.storageBytes - metrics.storageFreeBytes) / gib)))
12202
+ }
11564
12203
  };
11565
12204
  }
11566
12205
  async function heartbeatAgentOnce(options) {
@@ -11668,14 +12307,14 @@ function startAgentHeartbeat(options) {
11668
12307
  }
11669
12308
 
11670
12309
  // src/software-helper.ts
11671
- import { chmodSync as chmodSync14, existsSync as existsSync16, mkdirSync as mkdirSync12, unlinkSync as unlinkSync11 } from "fs";
12310
+ import { chmodSync as chmodSync14, existsSync as existsSync17, mkdirSync as mkdirSync13, unlinkSync as unlinkSync11 } from "fs";
11672
12311
  import { connect as connect6, createServer as createServer7 } from "net";
11673
- import { dirname as dirname8 } from "path";
12312
+ import { dirname as dirname10 } from "path";
11674
12313
 
11675
12314
  // src/service-supervisor.ts
11676
12315
  import { createHash as createHash9 } from "crypto";
11677
- import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync12, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync8, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "fs";
11678
- import { dirname as dirname6, join as join9, resolve as resolve4, sep as sep2 } from "path";
12316
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync12, readdirSync as readdirSync2, realpathSync as realpathSync2, renameSync as renameSync8, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "fs";
12317
+ import { dirname as dirname7, join as join9, resolve as resolve4, sep as sep2 } from "path";
11679
12318
  var SERVICE_STATE_DIRECTORY = "/var/lib/forgezero/services";
11680
12319
  var SERVICE_CONFIG_DIRECTORY = "/etc/forgezero/services";
11681
12320
  var SERVICE_UNIT_DIRECTORY = "/etc/systemd/system";
@@ -11685,14 +12324,14 @@ var statePath = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
11685
12324
  var within2 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep2}`);
11686
12325
  var defaultHost = {
11687
12326
  write(path2, content, mode) {
11688
- mkdirSync10(dirname6(path2), { recursive: true, mode: 493 });
12327
+ mkdirSync10(dirname7(path2), { recursive: true, mode: 493 });
11689
12328
  const next = `${path2}.next`;
11690
12329
  writeFileSync11(next, content, { mode });
11691
12330
  renameSync8(next, path2);
11692
12331
  },
11693
12332
  read: (path2) => readFileSync12(path2, "utf8"),
11694
- exists: existsSync14,
11695
- list: (path2) => existsSync14(path2) ? readdirSync2(path2) : [],
12333
+ exists: existsSync15,
12334
+ list: (path2) => existsSync15(path2) ? readdirSync2(path2) : [],
11696
12335
  realpath: realpathSync2,
11697
12336
  mkdir: (path2, mode) => mkdirSync10(path2, { recursive: true, mode }),
11698
12337
  remove: (path2) => rmSync7(path2, { force: true }),
@@ -11891,20 +12530,20 @@ async function activateSupervisedService(request, host = defaultHost) {
11891
12530
 
11892
12531
  // src/container-supervisor.ts
11893
12532
  import { createHash as createHash10 } from "crypto";
11894
- import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync13, readdirSync as readdirSync3, realpathSync as realpathSync3, renameSync as renameSync9, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "fs";
11895
- import { dirname as dirname7, resolve as resolve5, sep as sep3 } from "path";
12533
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync13, readdirSync as readdirSync3, realpathSync as realpathSync3, renameSync as renameSync9, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "fs";
12534
+ import { dirname as dirname8, resolve as resolve5, sep as sep3 } from "path";
11896
12535
  var defaultHost2 = {
11897
12536
  realpath: realpathSync3,
11898
- exists: existsSync15,
12537
+ exists: existsSync16,
11899
12538
  read: (path2) => readFileSync13(path2, "utf8"),
11900
12539
  write(path2, content, mode) {
11901
- mkdirSync11(dirname7(path2), { recursive: true, mode: 493 });
12540
+ mkdirSync11(dirname8(path2), { recursive: true, mode: 493 });
11902
12541
  const next = `${path2}.next`;
11903
12542
  writeFileSync12(next, content, { mode });
11904
12543
  renameSync9(next, path2);
11905
12544
  },
11906
12545
  remove: (path2) => rmSync8(path2, { force: true }),
11907
- list: (path2) => existsSync15(path2) ? readdirSync3(path2) : [],
12546
+ list: (path2) => existsSync16(path2) ? readdirSync3(path2) : [],
11908
12547
  mkdir: (path2, mode) => mkdirSync11(path2, { recursive: true, mode }),
11909
12548
  async exec(argv2) {
11910
12549
  const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C" } });
@@ -11926,6 +12565,38 @@ var within3 = (root, path2) => path2 === root || path2.startsWith(`${root}${sep3
11926
12565
  var idFor2 = (key) => createHash10("sha256").update(key).digest("hex").slice(0, 24);
11927
12566
  var statePath2 = (id2) => `${SERVICE_STATE_DIRECTORY}/${id2}.json`;
11928
12567
  var nameFor = (id2, slot) => `forgezero-${id2}-slot${slot}`;
12568
+ function backendFor(application) {
12569
+ const runtime = application.runtime;
12570
+ if (runtime.kind !== "container")
12571
+ throw new Error("CONTAINER_RUNTIME_REQUIRED");
12572
+ if (runtime.provider === "forgezero.docker") {
12573
+ if (runtime.runtimeClass && runtime.runtimeClass !== "runc")
12574
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
12575
+ return { engine: "docker", runtimeClass: "runc", prefix: ["/usr/bin/docker"] };
12576
+ }
12577
+ if (runtime.provider === "forgezero.containerd") {
12578
+ if ((runtime.runtimeClass ?? "runc") !== "runc")
12579
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
12580
+ return {
12581
+ engine: "containerd",
12582
+ runtimeClass: "runc",
12583
+ prefix: ["/usr/local/bin/nerdctl", "--namespace", "forgezero"],
12584
+ runtimeName: "io.containerd.runc.v2"
12585
+ };
12586
+ }
12587
+ if (runtime.provider === "forgezero.kata") {
12588
+ if (runtime.runtimeClass !== "kata-qemu-snp")
12589
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_MISMATCH");
12590
+ return {
12591
+ engine: "containerd",
12592
+ runtimeClass: "kata-qemu-snp",
12593
+ prefix: ["/usr/local/bin/nerdctl", "--namespace", "forgezero"],
12594
+ runtimeName: "io.containerd.kata.v2"
12595
+ };
12596
+ }
12597
+ throw new Error("CONTAINER_RUNTIME_PROVIDER_UNSUPPORTED");
12598
+ }
12599
+ var command2 = (backend, ...argv2) => [...backend.prefix, ...argv2];
11929
12600
  function validateRequest(request, host) {
11930
12601
  if (!/^[A-Za-z0-9][A-Za-z0-9_.:@/-]{0,255}$/.test(request.key) || !/^[a-f0-9]{40}$/.test(request.revision) || !/^[a-z][A-Za-z0-9-]{0,62}$/.test(request.component))
11931
12602
  throw new Error("CONTAINER_IDENTITY_INVALID");
@@ -11936,6 +12607,7 @@ function validateRequest(request, host) {
11936
12607
  const application = request.application;
11937
12608
  if (application.kind !== "application" || application.runtime.kind !== "container")
11938
12609
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
12610
+ backendFor(application);
11939
12611
  if (!application.resources.cpu || !application.resources.memory || !application.resources.pids)
11940
12612
  throw new Error("CONTAINER_LIMITS_REQUIRED");
11941
12613
  if (application.runtime.security.privileged !== false || application.runtime.security.noNewPrivileges !== true || !application.runtime.security.dropCapabilities.includes("ALL"))
@@ -11957,12 +12629,13 @@ async function buildContainerImage(requestValue, host = defaultHost2) {
11957
12629
  const runtime = request.application.runtime;
11958
12630
  if (runtime.kind !== "container")
11959
12631
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
12632
+ const backend = backendFor(request.application);
11960
12633
  const source = runtime.image.source;
11961
12634
  if (source.kind === "registry") {
11962
12635
  if (!source.reference.includes("@sha256:"))
11963
12636
  throw new Error("CONTAINER_IMAGE_DIGEST_REQUIRED");
11964
- await checked6(host, ["/usr/bin/docker", "pull", "--quiet", source.reference], "CONTAINER_PULL_FAILED");
11965
- const digest2 = await checked6(host, ["/usr/bin/docker", "image", "inspect", "--format={{.Id}}", source.reference], "CONTAINER_INSPECT_FAILED");
12637
+ await checked6(host, command2(backend, "pull", "--quiet", source.reference), "CONTAINER_PULL_FAILED");
12638
+ const digest2 = await checked6(host, command2(backend, "image", "inspect", "--format={{.Id}}", source.reference), "CONTAINER_INSPECT_FAILED");
11966
12639
  if (!/^sha256:[a-f0-9]{64}$/.test(digest2))
11967
12640
  throw new Error("CONTAINER_DIGEST_INVALID");
11968
12641
  return { image: source.reference, digest: digest2 };
@@ -11972,22 +12645,8 @@ async function buildContainerImage(requestValue, host = defaultHost2) {
11972
12645
  if (!within3(request.release, context) || !within3(context, dockerfile))
11973
12646
  throw new Error("CONTAINER_BUILD_PATH_INVALID");
11974
12647
  const tag = `forgezero/${idFor2(request.key)}:${request.revision}`;
11975
- await checked6(host, [
11976
- "/usr/bin/docker",
11977
- "build",
11978
- "--pull",
11979
- ...request.noCache ? ["--no-cache"] : [],
11980
- "--file",
11981
- dockerfile,
11982
- "--tag",
11983
- tag,
11984
- "--label",
11985
- `net.forgezero.revision=${request.revision}`,
11986
- "--label",
11987
- `net.forgezero.component=${request.component}`,
11988
- context
11989
- ], "CONTAINER_BUILD_FAILED");
11990
- const digest = await checked6(host, ["/usr/bin/docker", "image", "inspect", "--format={{.Id}}", tag], "CONTAINER_INSPECT_FAILED");
12648
+ await checked6(host, command2(backend, "build", "--pull", ...request.noCache ? ["--no-cache"] : [], "--file", dockerfile, "--tag", tag, "--label", `net.forgezero.revision=${request.revision}`, "--label", `net.forgezero.component=${request.component}`, context), "CONTAINER_BUILD_FAILED");
12649
+ const digest = await checked6(host, command2(backend, "image", "inspect", "--format={{.Id}}", tag), "CONTAINER_INSPECT_FAILED");
11991
12650
  if (!/^sha256:[a-f0-9]{64}$/.test(digest))
11992
12651
  throw new Error("CONTAINER_DIGEST_INVALID");
11993
12652
  return { image: tag, digest };
@@ -12055,7 +12714,7 @@ ${service.websocket ? ` proxy_set_header Upgrade $http_upgrade;
12055
12714
  }
12056
12715
  `;
12057
12716
  }
12058
- function runArgv(request, id2, slot, port, networkName) {
12717
+ function runArgv(request, backend, id2, slot, port, networkName) {
12059
12718
  const { application } = request;
12060
12719
  const resources = application.resources;
12061
12720
  const runtime = application.runtime;
@@ -12063,35 +12722,7 @@ function runArgv(request, id2, slot, port, networkName) {
12063
12722
  throw new Error("CONTAINER_RUNTIME_REQUIRED");
12064
12723
  const memory = resources.memory;
12065
12724
  const network = application.network.container;
12066
- const argv2 = [
12067
- "/usr/bin/docker",
12068
- "run",
12069
- "--detach",
12070
- "--name",
12071
- nameFor(id2, slot),
12072
- "--restart",
12073
- "unless-stopped",
12074
- "--label",
12075
- `net.forgezero.key=${request.key}`,
12076
- "--label",
12077
- `net.forgezero.revision=${request.revision}`,
12078
- "--cpus",
12079
- String(resources.cpu.limit),
12080
- "--cpu-shares",
12081
- String(resources.cpu.weight ?? 100),
12082
- "--memory",
12083
- `${memory.limitMiB}m`,
12084
- ...memory.reservationMiB ? ["--memory-reservation", `${memory.reservationMiB}m`] : [],
12085
- ...memory.swap === "disabled" ? ["--memory-swap", `${memory.limitMiB}m`] : [],
12086
- "--pids-limit",
12087
- String(resources.pids.limit),
12088
- "--security-opt",
12089
- "no-new-privileges=true",
12090
- "--cap-drop",
12091
- "ALL",
12092
- ...runtime.security.root === "read-only" ? ["--read-only"] : [],
12093
- ...network.mode === "host" ? ["--network", "host", "--env", `FZ_APP_PORT=${port}`] : ["--network", networkName, "--publish", `127.0.0.1:${port}:${application.service.port}`]
12094
- ];
12725
+ const argv2 = command2(backend, "run", "--detach", "--name", nameFor(id2, slot), "--restart", "unless-stopped", "--label", `net.forgezero.key=${request.key}`, "--label", `net.forgezero.revision=${request.revision}`, ...backend.runtimeName ? ["--runtime", backend.runtimeName] : [], "--cpus", String(resources.cpu.limit), "--cpu-shares", String(resources.cpu.weight ?? 100), "--memory", `${memory.limitMiB}m`, ...memory.reservationMiB ? ["--memory-reservation", `${memory.reservationMiB}m`] : [], ...memory.swap === "disabled" ? ["--memory-swap", `${memory.limitMiB}m`] : [], "--pids-limit", String(resources.pids.limit), "--security-opt", "no-new-privileges=true", "--cap-drop", "ALL", ...runtime.security.root === "read-only" ? ["--read-only"] : [], ...network.mode === "host" ? ["--network", "host", "--env", `FZ_APP_PORT=${port}`] : ["--network", networkName, "--publish", `127.0.0.1:${port}:${application.service.port}`]);
12095
12726
  for (const mount of application.storage ?? []) {
12096
12727
  if (mount.class === "ephemeral")
12097
12728
  argv2.push("--tmpfs", `${mount.path}:rw,noexec,nosuid,size=${mount.sizeMiB}m`);
@@ -12105,6 +12736,7 @@ async function activateContainer(requestValue, host = defaultHost2) {
12105
12736
  const request = validateRequest(requestValue, host);
12106
12737
  if (!/^sha256:[a-f0-9]{64}$/.test(request.image) && !/^forgezero\/[a-f0-9]{24}:[a-f0-9]{40}$/.test(request.image) && !request.image.includes("@sha256:"))
12107
12738
  throw new Error("CONTAINER_IMAGE_INVALID");
12739
+ const backend = backendFor(request.application);
12108
12740
  const id2 = idFor2(request.key);
12109
12741
  host.mkdir(SERVICE_STATE_DIRECTORY, 448);
12110
12742
  host.mkdir(SERVICE_NGINX_DIRECTORY, 493);
@@ -12117,9 +12749,9 @@ async function activateContainer(requestValue, host = defaultHost2) {
12117
12749
  if (containerNetwork.mode === "bridge") {
12118
12750
  networkName = containerNetwork.network ? `forgezero-${id2}-${containerNetwork.network}` : "bridge";
12119
12751
  if (networkName !== "bridge") {
12120
- const inspected = await host.exec(["/usr/bin/docker", "network", "inspect", '--format={{index .Labels "net.forgezero.owner"}}', networkName]);
12752
+ const inspected = await host.exec(command2(backend, "network", "inspect", '--format={{index .Labels "net.forgezero.owner"}}', networkName));
12121
12753
  if (inspected.exitCode !== 0) {
12122
- await checked6(host, ["/usr/bin/docker", "network", "create", "--driver", "bridge", "--label", `net.forgezero.owner=${id2}`, networkName], "CONTAINER_NETWORK_CREATE_FAILED");
12754
+ await checked6(host, command2(backend, "network", "create", "--driver", "bridge", "--label", `net.forgezero.owner=${id2}`, networkName), "CONTAINER_NETWORK_CREATE_FAILED");
12123
12755
  } else if (inspected.output.trim() !== id2)
12124
12756
  throw new Error("CONTAINER_NETWORK_OWNERSHIP_INVALID");
12125
12757
  }
@@ -12128,8 +12760,8 @@ async function activateContainer(requestValue, host = defaultHost2) {
12128
12760
  const nextSlot = slots === 2 && previous?.activeSlot === 0 ? 1 : 0;
12129
12761
  const port = ports[nextSlot];
12130
12762
  const name = nameFor(id2, nextSlot);
12131
- await host.exec(["/usr/bin/docker", "rm", "--force", name]);
12132
- await checked6(host, runArgv(request, id2, nextSlot, port, networkName), "CONTAINER_START_FAILED");
12763
+ await host.exec(command2(backend, "rm", "--force", name));
12764
+ await checked6(host, runArgv(request, backend, id2, nextSlot, port, networkName), "CONTAINER_START_FAILED");
12133
12765
  let healthy = false;
12134
12766
  const health = request.application.service.health;
12135
12767
  for (let attempt = 0;attempt < (health.attempts ?? 30); attempt += 1) {
@@ -12140,9 +12772,22 @@ async function activateContainer(requestValue, host = defaultHost2) {
12140
12772
  await host.sleep(health.intervalMs ?? 1000);
12141
12773
  }
12142
12774
  if (!healthy) {
12143
- await host.exec(["/usr/bin/docker", "rm", "--force", name]);
12775
+ await host.exec(command2(backend, "rm", "--force", name));
12144
12776
  throw new Error("CONTAINER_HEALTH_FAILED");
12145
12777
  }
12778
+ let runtimeEvidence;
12779
+ if (backend.engine === "containerd") {
12780
+ const raw = await checked6(host, ["/usr/bin/ctr", "--namespace", "forgezero", "containers", "info", name], "CONTAINER_RUNTIME_EVIDENCE_FAILED");
12781
+ let observed = "";
12782
+ try {
12783
+ observed = String(JSON.parse(raw).Runtime?.Name ?? "");
12784
+ } catch {
12785
+ throw new Error("CONTAINER_RUNTIME_EVIDENCE_INVALID");
12786
+ }
12787
+ if (observed !== backend.runtimeName)
12788
+ throw new Error("CONTAINER_RUNTIME_DOWNGRADE");
12789
+ runtimeEvidence = observed;
12790
+ }
12146
12791
  const nginxPath = `${SERVICE_NGINX_DIRECTORY}/forgezero-${id2}.conf`;
12147
12792
  const previousNginx = host.exists(nginxPath) ? host.read(nginxPath) : null;
12148
12793
  host.write(nginxPath, nginx2(request, id2, port), 420);
@@ -12156,7 +12801,7 @@ async function activateContainer(requestValue, host = defaultHost2) {
12156
12801
  host.write(nginxPath, previousNginx, 420);
12157
12802
  await host.exec(["/usr/sbin/nginx", "-t"]);
12158
12803
  await host.exec(["/usr/bin/systemctl", "reload", "nginx.service"]);
12159
- await host.exec(["/usr/bin/docker", "rm", "--force", name]);
12804
+ await host.exec(command2(backend, "rm", "--force", name));
12160
12805
  throw cause;
12161
12806
  }
12162
12807
  const state = {
@@ -12169,17 +12814,168 @@ async function activateContainer(requestValue, host = defaultHost2) {
12169
12814
  applicationPorts: ports,
12170
12815
  activeSlot: nextSlot,
12171
12816
  healthPath: health.path,
12817
+ containerRuntime: {
12818
+ engine: backend.engine,
12819
+ runtimeClass: backend.runtimeClass,
12820
+ ...backend.engine === "containerd" ? { namespace: "forgezero", evidence: runtimeEvidence } : {}
12821
+ },
12172
12822
  updatedAtTs: host.now()
12173
12823
  };
12174
12824
  host.write(statePath2(id2), `${JSON.stringify(state, null, 2)}
12175
12825
  `, 384);
12176
12826
  if (previous && previous.activeSlot !== nextSlot) {
12177
12827
  await host.sleep(request.application.rollout.drainMs ?? 30000);
12178
- await host.exec(["/usr/bin/docker", "rm", "--force", nameFor(id2, previous.activeSlot)]);
12828
+ await host.exec(command2(backend, "rm", "--force", nameFor(id2, previous.activeSlot)));
12179
12829
  }
12180
12830
  return state;
12181
12831
  }
12182
12832
 
12833
+ // src/deployment-connectivity.ts
12834
+ import { createHash as createHash11 } from "crypto";
12835
+ import { mkdirSync as mkdirSync12, renameSync as renameSync10, writeFileSync as writeFileSync13 } from "fs";
12836
+ import { dirname as dirname9 } from "path";
12837
+ var TOKEN2 = /^[A-Za-z0-9._-]{40,16384}$/;
12838
+ var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
12839
+ var HOSTNAME2 = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
12840
+ var NAME3 = /^[a-z][a-z0-9._-]{0,99}$/;
12841
+ var idFor3 = (key) => createHash11("sha256").update(key).digest("hex").slice(0, 20);
12842
+ var checked7 = async (host, argv2, label) => {
12843
+ const result = await host.exec(argv2);
12844
+ if (result.exitCode !== 0)
12845
+ throw new Error(`${label} failed: ${result.output.slice(0, 512)}`);
12846
+ return result.output.trim();
12847
+ };
12848
+ async function sealWithSystemd(name, path2, value) {
12849
+ mkdirSync12(dirname9(path2), { recursive: true, mode: 448 });
12850
+ const next = `${path2}.next`;
12851
+ const child = Bun.spawn(["/usr/bin/systemd-creds", "encrypt", `--name=${name}`, "-", next], {
12852
+ stdin: "pipe",
12853
+ stdout: "pipe",
12854
+ stderr: "pipe",
12855
+ env: { PATH: "/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C" }
12856
+ });
12857
+ child.stdin.write(value);
12858
+ child.stdin.end();
12859
+ const [stdout, stderr, exitCode] = await Promise.all([
12860
+ new Response(child.stdout).text(),
12861
+ new Response(child.stderr).text(),
12862
+ child.exited
12863
+ ]);
12864
+ if (exitCode !== 0)
12865
+ throw new Error(`systemd credential sealing failed: ${`${stdout}${stderr}`.replaceAll(value, "[REDACTED]").slice(0, 512)}`);
12866
+ renameSync10(next, path2);
12867
+ }
12868
+ var defaultHost3 = {
12869
+ seal: sealWithSystemd,
12870
+ write(path2, content, mode) {
12871
+ mkdirSync12(dirname9(path2), { recursive: true, mode: 493 });
12872
+ const next = `${path2}.next`;
12873
+ writeFileSync13(next, content, { mode });
12874
+ renameSync10(next, path2);
12875
+ },
12876
+ async exec(argv2) {
12877
+ const child = Bun.spawn([...argv2], { stdout: "pipe", stderr: "pipe", env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", LANG: "C", LC_ALL: "C" } });
12878
+ const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
12879
+ return { exitCode, output: `${stdout}${stderr}` };
12880
+ },
12881
+ sleep: (ms) => Bun.sleep(ms)
12882
+ };
12883
+ function validate2(request) {
12884
+ if (!request.key || request.key.length > 256)
12885
+ throw new Error("deployment connectivity key is invalid");
12886
+ const publicIntent = request.intent.public;
12887
+ const privateIntent = request.intent.private;
12888
+ if (publicIntent?.mode === "cloudflare-tunnel") {
12889
+ if (!NAME3.test(publicIntent.credential) || !HOSTNAME2.test(publicIntent.hostname) || !NAME3.test(publicIntent.tunnelKey) || publicIntent.service.protocol !== "http" || !Number.isInteger(publicIntent.service.port) || publicIntent.service.port < 1 || publicIntent.service.port > 65535 || !request.capabilities.public || !UUID.test(request.capabilities.public.tunnelId) || !TOKEN2.test(request.capabilities.public.connectorToken)) {
12890
+ throw new Error("public deployment connectivity is malformed");
12891
+ }
12892
+ } else if (request.capabilities.public)
12893
+ throw new Error("unexpected public deployment capability");
12894
+ if (privateIntent?.mode === "cloudflare-warp") {
12895
+ if (!NAME3.test(privateIntent.credential) || privateIntent.network.length < 1 || privateIntent.network.length > 128 || !request.capabilities.private || !UUID.test(request.capabilities.private.connectorId) || !TOKEN2.test(request.capabilities.private.connectorToken)) {
12896
+ throw new Error("private deployment connectivity is malformed");
12897
+ }
12898
+ } else if (request.capabilities.private)
12899
+ throw new Error("unexpected private deployment capability");
12900
+ }
12901
+ async function applyDeploymentConnectivity(request, host = defaultHost3) {
12902
+ validate2(request);
12903
+ const id2 = idFor3(request.key);
12904
+ const evidence = { key: request.key };
12905
+ if (request.intent.public?.mode === "cloudflare-tunnel") {
12906
+ const capability = request.capabilities.public;
12907
+ const credentialName = `FZ_TUNNEL_${id2.toUpperCase()}`;
12908
+ const credentialPath = `/etc/forgezero/creds/deployment-tunnel-${id2}.cred`;
12909
+ const unit2 = `forgezero-cloudflared-${id2}.service`;
12910
+ const unitPath = `/etc/systemd/system/${unit2}`;
12911
+ const metricsPort = 20000 + Number.parseInt(id2.slice(0, 4), 16) % 20000;
12912
+ await host.seal(credentialName, credentialPath, capability.connectorToken);
12913
+ host.write(unitPath, `[Unit]
12914
+ Description=ForgeZero deployment Tunnel ${id2}
12915
+ After=network-online.target
12916
+ Wants=network-online.target
12917
+
12918
+ [Service]
12919
+ Type=simple
12920
+ DynamicUser=yes
12921
+ LoadCredentialEncrypted=${credentialName}:${credentialPath}
12922
+ ExecStart=/usr/local/bin/cloudflared tunnel --no-autoupdate --metrics 127.0.0.1:${metricsPort} run --token-file %d/${credentialName}
12923
+ Restart=always
12924
+ RestartSec=5
12925
+ NoNewPrivileges=true
12926
+ PrivateTmp=true
12927
+ ProtectSystem=strict
12928
+ ProtectHome=true
12929
+
12930
+ [Install]
12931
+ WantedBy=multi-user.target
12932
+ `, 420);
12933
+ await checked7(host, ["/usr/bin/systemctl", "daemon-reload"], "cloudflared daemon reload");
12934
+ await checked7(host, ["/usr/bin/systemctl", "enable", "--now", unit2], "cloudflared start");
12935
+ await checked7(host, ["/usr/bin/systemctl", "is-active", "--quiet", unit2], "cloudflared readiness");
12936
+ evidence.public = { hostname: request.intent.public.hostname, tunnelId: capability.tunnelId, unit: unit2, active: true };
12937
+ }
12938
+ if (request.intent.private?.mode === "cloudflare-warp") {
12939
+ const capability = request.capabilities.private;
12940
+ const credentialPath = "/etc/forgezero/creds/CF_WARP_CONNECTOR_TOKEN.cred";
12941
+ const unit2 = "forgezero-deployment-mesh.service";
12942
+ await host.seal("CF_WARP_CONNECTOR_TOKEN", credentialPath, capability.connectorToken);
12943
+ host.write(`/etc/systemd/system/${unit2}`, `[Unit]
12944
+ Description=ForgeZero deployment Mesh/WARP connector
12945
+ Requires=warp-svc.service
12946
+ After=network-online.target warp-svc.service
12947
+ Wants=network-online.target
12948
+
12949
+ [Service]
12950
+ Type=oneshot
12951
+ RemainAfterExit=yes
12952
+ LoadCredentialEncrypted=CF_WARP_CONNECTOR_TOKEN:${credentialPath}
12953
+ ExecStart=/usr/local/bin/fz-agent mesh-config
12954
+ NoNewPrivileges=true
12955
+ PrivateTmp=true
12956
+ ProtectSystem=strict
12957
+ ProtectHome=true
12958
+ ReadWritePaths=/var/lib/cloudflare-warp
12959
+
12960
+ [Install]
12961
+ WantedBy=multi-user.target
12962
+ `, 420);
12963
+ await checked7(host, ["/usr/bin/systemctl", "daemon-reload"], "WARP daemon reload");
12964
+ await checked7(host, ["/usr/bin/systemctl", "enable", "--now", "warp-svc.service", unit2], "WARP connector start");
12965
+ for (let attempt = 0;attempt < 10; attempt += 1) {
12966
+ const status = await host.exec(["/usr/bin/warp-cli", "--accept-tos", "status"]);
12967
+ if (status.exitCode === 0 && /\bconnected\b/i.test(status.output) && !/\bdisconnected\b/i.test(status.output)) {
12968
+ evidence.private = { network: request.intent.private.network, connectorId: capability.connectorId, unit: unit2, active: true };
12969
+ break;
12970
+ }
12971
+ await host.sleep(1000);
12972
+ }
12973
+ if (!evidence.private)
12974
+ throw new Error("WARP connector readiness failed");
12975
+ }
12976
+ return evidence;
12977
+ }
12978
+
12183
12979
  // src/software-helper.ts
12184
12980
  var DEFAULT_SOFTWARE_HELPER_SOCKET = "/run/forgezero-software/helper.sock";
12185
12981
  var SOFTWARE_HELPER_GROUP = "forgezero-software";
@@ -12188,13 +12984,14 @@ var MAX_REQUEST_BYTES6 = 128 * 1024;
12188
12984
  var MAX_PENDING_REQUESTS = 128;
12189
12985
  function startSoftwareHelper(options = {}) {
12190
12986
  const socketPath = options.socketPath ?? DEFAULT_SOFTWARE_HELPER_SOCKET;
12191
- if (existsSync16(socketPath))
12987
+ if (existsSync17(socketPath))
12192
12988
  unlinkSync11(socketPath);
12193
- mkdirSync12(dirname8(socketPath), { recursive: true, mode: 488 });
12989
+ mkdirSync13(dirname10(socketPath), { recursive: true, mode: 488 });
12194
12990
  const ensure = options.ensure ?? ensureSoftwareRequirements;
12195
12991
  const activate = options.activate ?? activateSupervisedService;
12196
12992
  const buildContainer = options.buildContainer ?? buildContainerImage;
12197
12993
  const activateTypedContainer = options.activateContainer ?? activateContainer;
12994
+ const applyConnectivity = options.applyConnectivity ?? applyDeploymentConnectivity;
12198
12995
  let tail = Promise.resolve();
12199
12996
  let pending = 0;
12200
12997
  const server = createServer7((socket) => {
@@ -12251,6 +13048,12 @@ function startSoftwareHelper(options = {}) {
12251
13048
  throw new Error("container deployment root is not owned by this helper");
12252
13049
  const state = await activateTypedContainer(containerRequest);
12253
13050
  socket.end(`${JSON.stringify({ ok: true, state })}
13051
+ `);
13052
+ return;
13053
+ }
13054
+ if (request.op === "apply-connectivity" && request.request) {
13055
+ const result = await applyConnectivity(request.request);
13056
+ socket.end(`${JSON.stringify({ ok: true, result })}
12254
13057
  `);
12255
13058
  return;
12256
13059
  }
@@ -12308,6 +13111,9 @@ function requestContainerBuild(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOC
12308
13111
  function requestContainerActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
12309
13112
  return requestContainer("activate-container", request, socketPath, timeoutMs);
12310
13113
  }
13114
+ function requestDeploymentConnectivity(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 15 * 60000) {
13115
+ return requestContainer("apply-connectivity", request, socketPath, timeoutMs);
13116
+ }
12311
13117
  function requestServiceActivation(request, socketPath = DEFAULT_SOFTWARE_HELPER_SOCKET, timeoutMs = 10 * 60000) {
12312
13118
  return new Promise((resolve6, reject) => {
12313
13119
  const socket = connect6(socketPath, () => socket.write(`${JSON.stringify({ op: "activate-service", request })}
@@ -12521,9 +13327,9 @@ var systemCommand = (argv2, stdin) => {
12521
13327
  stderr: result.stderr.toString()
12522
13328
  };
12523
13329
  };
12524
- function resolveServiceUid(user, command2 = systemCommand) {
13330
+ function resolveServiceUid(user, command3 = systemCommand) {
12525
13331
  assertServiceUser(user);
12526
- const result = command2(["/usr/bin/id", "-u", user]);
13332
+ const result = command3(["/usr/bin/id", "-u", user]);
12527
13333
  const uid = Number(result.stdout.trim());
12528
13334
  if (result.exitCode !== 0 || !/^\d+$/.test(result.stdout.trim())) {
12529
13335
  throw new Error(`Agent egress service account ${user} does not exist.`);
@@ -12548,14 +13354,14 @@ function assertResolvedStub(realpath = realpathSync4) {
12548
13354
  throw new Error("Agent egress requires the active systemd-resolved stub.");
12549
13355
  }
12550
13356
  }
12551
- var listPolicy = (command2) => command2(["/usr/sbin/nft", "--numeric", "list", "table", "inet", AGENT_EGRESS_TABLE]);
12552
- function verifyAgentEgressPolicy(uids, command2 = systemCommand, loopback) {
13357
+ var listPolicy = (command3) => command3(["/usr/sbin/nft", "--numeric", "list", "table", "inet", AGENT_EGRESS_TABLE]);
13358
+ function verifyAgentEgressPolicy(uids, command3 = systemCommand, loopback) {
12553
13359
  if (uids.length < 1)
12554
13360
  return false;
12555
13361
  for (const uid of uids)
12556
13362
  assertUid(uid);
12557
13363
  const uniqueUids = [...new Set(uids)].sort((left, right) => left - right);
12558
- const result = listPolicy(command2);
13364
+ const result = listPolicy(command3);
12559
13365
  if (result.exitCode !== 0)
12560
13366
  return false;
12561
13367
  let loopbackComment = "";
@@ -12601,44 +13407,44 @@ function applyAgentEgressPolicy(options) {
12601
13407
  if (typeof getuid !== "function" || getuid() !== 0) {
12602
13408
  throw new Error("Agent egress policy must run as root.");
12603
13409
  }
12604
- const command2 = options.command ?? systemCommand;
13410
+ const command3 = options.command ?? systemCommand;
12605
13411
  assertResolvedStub(options.realpath);
12606
13412
  if (options.users.length < 1)
12607
13413
  throw new Error("Agent egress policy needs at least one service user.");
12608
- const uids = [...new Set(options.users.map((user) => resolveServiceUid(user, command2)))];
13414
+ const uids = [...new Set(options.users.map((user) => resolveServiceUid(user, command3)))];
12609
13415
  const loopback = options.loopback ? {
12610
- uid: resolveServiceUid(options.loopback.user, command2),
13416
+ uid: resolveServiceUid(options.loopback.user, command3),
12611
13417
  tcpPorts: normalizeEgressTcpPorts(options.loopback.tcpPorts),
12612
13418
  publicTcpPorts: options.loopback.publicTcpPorts === undefined ? undefined : normalizeEgressTcpPorts(options.loopback.publicTcpPorts)
12613
13419
  } : undefined;
12614
13420
  if (loopback && !uids.includes(loopback.uid))
12615
13421
  uids.push(loopback.uid);
12616
- const current2 = listPolicy(command2);
12617
- const applied = command2(["/usr/sbin/nft", "--file", "-"], renderAgentEgressNft(uids, current2.exitCode === 0, loopback));
13422
+ const current2 = listPolicy(command3);
13423
+ const applied = command3(["/usr/sbin/nft", "--file", "-"], renderAgentEgressNft(uids, current2.exitCode === 0, loopback));
12618
13424
  if (applied.exitCode !== 0) {
12619
13425
  throw new Error(`Could not apply Agent egress policy: ${applied.stderr.trim() || "nft refused it"}`);
12620
13426
  }
12621
- if (!verifyAgentEgressPolicy(uids, command2, loopback)) {
13427
+ if (!verifyAgentEgressPolicy(uids, command3, loopback)) {
12622
13428
  throw new Error("Agent egress policy did not verify after installation.");
12623
13429
  }
12624
13430
  return uids;
12625
13431
  }
12626
13432
  async function superviseAgentEgressPolicy(options) {
12627
- const command2 = options.command ?? systemCommand;
13433
+ const command3 = options.command ?? systemCommand;
12628
13434
  const uids = applyAgentEgressPolicy({
12629
13435
  users: options.users,
12630
13436
  loopback: options.loopback,
12631
- command: command2,
13437
+ command: command3,
12632
13438
  realpath: options.realpath,
12633
13439
  getuid: options.getuid
12634
13440
  });
12635
13441
  const loopback = options.loopback ? {
12636
- uid: resolveServiceUid(options.loopback.user, command2),
13442
+ uid: resolveServiceUid(options.loopback.user, command3),
12637
13443
  tcpPorts: normalizeEgressTcpPorts(options.loopback.tcpPorts),
12638
13444
  publicTcpPorts: options.loopback.publicTcpPorts === undefined ? undefined : normalizeEgressTcpPorts(options.loopback.publicTcpPorts)
12639
13445
  } : undefined;
12640
13446
  (options.notifyReady ?? (() => {
12641
- const result = command2(["/usr/bin/systemd-notify", "--ready"]);
13447
+ const result = command3(["/usr/bin/systemd-notify", "--ready"]);
12642
13448
  if (result.exitCode !== 0)
12643
13449
  throw new Error("Could not notify systemd that Agent egress is ready.");
12644
13450
  }))();
@@ -12649,7 +13455,7 @@ async function superviseAgentEgressPolicy(options) {
12649
13455
  }
12650
13456
  for (;; ) {
12651
13457
  await wait(intervalMs);
12652
- if (!verifyAgentEgressPolicy(uids, command2, loopback)) {
13458
+ if (!verifyAgentEgressPolicy(uids, command3, loopback)) {
12653
13459
  throw new Error("Agent egress policy disappeared or changed; stopping the bound Agent.");
12654
13460
  }
12655
13461
  }
@@ -13260,7 +14066,7 @@ var AGENT_CREDENTIAL_POLICY = {
13260
14066
  "platform-compute": { vault: true, systemdFallback: true, hiddenInput: false },
13261
14067
  "tenant-compute": { vault: true, systemdFallback: true, hiddenInput: false }
13262
14068
  };
13263
- var CREDENTIAL_NAME = /^[A-Z_][A-Z0-9_]*$/;
14069
+ var CREDENTIAL_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
13264
14070
  var SCOPE_PART2 = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
13265
14071
  function deploymentCredentialSchema(input) {
13266
14072
  if (!SCOPE_PART2.test(input.projectKey) || !SCOPE_PART2.test(input.environmentKey)) {
@@ -13302,17 +14108,17 @@ import {
13302
14108
  chmodSync as chmodSync15,
13303
14109
  chownSync,
13304
14110
  copyFileSync as copyFileSync2,
13305
- existsSync as existsSync17,
14111
+ existsSync as existsSync18,
13306
14112
  lstatSync as lstatSync3,
13307
- mkdirSync as mkdirSync13,
14113
+ mkdirSync as mkdirSync14,
13308
14114
  readFileSync as readFileSync14,
13309
14115
  readdirSync as readdirSync4,
13310
14116
  realpathSync as realpathSync5,
13311
- renameSync as renameSync10,
14117
+ renameSync as renameSync11,
13312
14118
  rmSync as rmSync9,
13313
- statSync as statSync3,
14119
+ statSync as statSync4,
13314
14120
  symlinkSync as symlinkSync4,
13315
- writeFileSync as writeFileSync13
14121
+ writeFileSync as writeFileSync14
13316
14122
  } from "fs";
13317
14123
  import { join as join10 } from "path";
13318
14124
  var boundedInteger2 = (name, value, minimum, maximum) => {
@@ -13372,7 +14178,7 @@ var replaceLink = (path2, target2) => {
13372
14178
  return;
13373
14179
  }
13374
14180
  symlinkSync4(target2, pending);
13375
- renameSync10(pending, path2);
14181
+ renameSync11(pending, path2);
13376
14182
  };
13377
14183
  var secureRelease = (path2, uid, gid) => {
13378
14184
  const visit = (current2) => {
@@ -13395,7 +14201,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13395
14201
  if (!release.startsWith(`${releases}/`) || release === releases)
13396
14202
  throw new Error("release is outside configured releases directory");
13397
14203
  for (const required of ["forgezero.deploy.ts", ".fz/deploy.plan.json", "src/index.ts", "bun.lock"]) {
13398
- const metadata = statSync3(join10(release, required));
14204
+ const metadata = statSync4(join10(release, required));
13399
14205
  if (!metadata.isFile() || metadata.size < 1)
13400
14206
  throw new Error(`release is incomplete: ${required}`);
13401
14207
  }
@@ -13403,9 +14209,9 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13403
14209
  const request = options.fetch ?? fetch;
13404
14210
  const sleep2 = options.sleep ?? Bun.sleep;
13405
14211
  const slots = join10(normalized.root, "slots");
13406
- mkdirSync13(slots, { recursive: true, mode: 493 });
14212
+ mkdirSync14(slots, { recursive: true, mode: 493 });
13407
14213
  const slotFile = join10(normalized.root, ".forge-slot");
13408
- const previousSlot = existsSync17(slotFile) ? readFileSync14(slotFile, "utf8").trim() : undefined;
14214
+ const previousSlot = existsSync18(slotFile) ? readFileSync14(slotFile, "utf8").trim() : undefined;
13409
14215
  const target2 = previousSlot === "blue" ? "green" : "blue";
13410
14216
  const port = target2 === "blue" ? normalized.bluePort : normalized.greenPort;
13411
14217
  const targetLink = join10(slots, target2);
@@ -13445,17 +14251,17 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13445
14251
  }
13446
14252
  const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
13447
14253
  const backup = `${upstream}.forgezero-backup`;
13448
- if (existsSync17(upstream))
14254
+ if (existsSync18(upstream))
13449
14255
  copyFileSync2(upstream, backup);
13450
14256
  else
13451
14257
  rmSync9(backup, { force: true });
13452
- writeFileSync13(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
14258
+ writeFileSync14(upstream, `upstream forgezero { server 127.0.0.1:${port}; }
13453
14259
  `, { mode: 420 });
13454
14260
  const test = await exec(["/usr/sbin/nginx", "-t"]);
13455
14261
  const reload = test.exitCode === 0 ? await exec(["/usr/sbin/nginx", "-s", "reload"]) : test;
13456
14262
  if (reload.exitCode !== 0) {
13457
- if (existsSync17(backup))
13458
- renameSync10(backup, upstream);
14263
+ if (existsSync18(backup))
14264
+ renameSync11(backup, upstream);
13459
14265
  else
13460
14266
  rmSync9(upstream, { force: true });
13461
14267
  await exec(["/usr/sbin/nginx", "-t"]);
@@ -13464,13 +14270,13 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13464
14270
  throw new Error("nginx refused the promoted upstream");
13465
14271
  }
13466
14272
  rmSync9(backup, { force: true });
13467
- writeFileSync13(slotFile, `${target2}
14273
+ writeFileSync14(slotFile, `${target2}
13468
14274
  `, { mode: 420 });
13469
14275
  if (previousSlot && previousSlot !== target2) {
13470
14276
  await sleep2(normalized.drainDeadlineMs);
13471
14277
  await exec(["/usr/bin/systemctl", "stop", `forgezero@${previousSlot}.service`]);
13472
14278
  }
13473
- const old = readdirSync4(releases, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join10(releases, entry.name)).sort((left, right) => statSync3(right).mtimeMs - statSync3(left).mtimeMs).slice(normalized.keepReleases);
14279
+ const old = readdirSync4(releases, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join10(releases, entry.name)).sort((left, right) => statSync4(right).mtimeMs - statSync4(left).mtimeMs).slice(normalized.keepReleases);
13474
14280
  for (const path2 of old)
13475
14281
  if (path2 !== release)
13476
14282
  rmSync9(path2, { recursive: true, force: true });
@@ -13478,7 +14284,7 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
13478
14284
  }
13479
14285
 
13480
14286
  // src/recovery-host.ts
13481
- import { existsSync as existsSync18, readFileSync as readFileSync15, statSync as statSync4 } from "fs";
14287
+ import { existsSync as existsSync19, readFileSync as readFileSync15, statSync as statSync5 } from "fs";
13482
14288
  import { hostname } from "os";
13483
14289
  import { join as join11 } from "path";
13484
14290
  var execute2 = async (argv2) => {
@@ -13486,7 +14292,7 @@ var execute2 = async (argv2) => {
13486
14292
  const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]);
13487
14293
  return { stdout, stderr, exitCode };
13488
14294
  };
13489
- var checked7 = async (run2, argv2, expected = 0) => {
14295
+ var checked8 = async (run2, argv2, expected = 0) => {
13490
14296
  const result = await run2(argv2);
13491
14297
  if (result.exitCode !== expected)
13492
14298
  throw new Error(`${argv2[0]} failed (${result.exitCode})`);
@@ -13504,7 +14310,7 @@ var exactState = (path2, expected) => {
13504
14310
  throw new Error(`state mismatch: ${expected}`);
13505
14311
  };
13506
14312
  var nonEmpty = (path2) => {
13507
- if (!existsSync18(path2) || !statSync4(path2).isFile() || statSync4(path2).size < 1)
14313
+ if (!existsSync19(path2) || !statSync5(path2).isFile() || statSync5(path2).size < 1)
13508
14314
  throw new Error(`required file missing: ${path2}`);
13509
14315
  };
13510
14316
  var option = (args, name) => {
@@ -13548,29 +14354,29 @@ async function runRecoveryHost(args, run2 = execute2) {
13548
14354
  const slot = activeSlot();
13549
14355
  nonEmpty(join11("/opt/forgezero/slots", slot, "src/server/maintenance/restore-backup.ts"));
13550
14356
  nonEmpty(join11("/opt/forgezero/slots", slot, "src/server/maintenance/verify-recovery-cluster.ts"));
13551
- await checked7(run2, ["/usr/bin/test", "-x", "/usr/local/bin/fz"]);
13552
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-db.service"], hasDatabase ? 0 : 3);
14357
+ await checked8(run2, ["/usr/bin/test", "-x", "/usr/local/bin/fz"]);
14358
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-db.service"], hasDatabase ? 0 : 3);
13553
14359
  if (args.includes("--require-stopped")) {
13554
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@blue.service"], 3);
13555
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@green.service"], 3);
14360
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@blue.service"], 3);
14361
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@green.service"], 3);
13556
14362
  }
13557
14363
  return;
13558
14364
  }
13559
14365
  if (action === "fence") {
13560
- await checked7(run2, ["/usr/bin/systemctl", "stop", "forgezero@blue.service", "forgezero@green.service"]);
13561
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@blue.service"], 3);
13562
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@green.service"], 3);
14366
+ await checked8(run2, ["/usr/bin/systemctl", "stop", "forgezero@blue.service", "forgezero@green.service"]);
14367
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@blue.service"], 3);
14368
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero@green.service"], 3);
13563
14369
  const role = option(args, "db-role");
13564
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-db.service"], role === "none" ? 3 : 0);
14370
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", "forgezero-db.service"], role === "none" ? 3 : 0);
13565
14371
  return;
13566
14372
  }
13567
14373
  if (action === "start-active") {
13568
14374
  const slot = activeSlot();
13569
14375
  const inactive = slot === "blue" ? "green" : "blue";
13570
- await checked7(run2, ["/usr/bin/systemctl", "stop", `forgezero@${inactive}.service`]);
13571
- await checked7(run2, ["/usr/bin/systemctl", "start", `forgezero@${slot}.service`]);
13572
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", `forgezero@${slot}.service`]);
13573
- await checked7(run2, ["/usr/bin/systemctl", "is-active", "--quiet", `forgezero@${inactive}.service`], 3);
14376
+ await checked8(run2, ["/usr/bin/systemctl", "stop", `forgezero@${inactive}.service`]);
14377
+ await checked8(run2, ["/usr/bin/systemctl", "start", `forgezero@${slot}.service`]);
14378
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", `forgezero@${slot}.service`]);
14379
+ await checked8(run2, ["/usr/bin/systemctl", "is-active", "--quiet", `forgezero@${inactive}.service`], 3);
13574
14380
  return;
13575
14381
  }
13576
14382
  if (action === "maintenance") {
@@ -13593,7 +14399,7 @@ async function runRecoveryHost(args, run2 = execute2) {
13593
14399
  "--property=LoadCredentialEncrypted=backup-recovery-root:/etc/forgezero/creds/backup-recovery-root.cred"
13594
14400
  ] : []
13595
14401
  ];
13596
- await checked7(run2, ["/usr/bin/systemd-run", "--quiet", "--wait", "--pipe", "--collect", `--unit=${unit2(option(args, "unit"))}`, ...properties, "/usr/local/bin/fz", ...fzArgs]);
14402
+ await checked8(run2, ["/usr/bin/systemd-run", "--quiet", "--wait", "--pipe", "--collect", `--unit=${unit2(option(args, "unit"))}`, ...properties, "/usr/local/bin/fz", ...fzArgs]);
13597
14403
  return;
13598
14404
  }
13599
14405
  throw new Error("unknown recovery-host action");
@@ -13605,15 +14411,15 @@ import { lstatSync as lstatSync6 } from "fs";
13605
14411
  // src/bootstrap.ts
13606
14412
  import {
13607
14413
  chmodSync as chmodSync17,
13608
- existsSync as existsSync20,
14414
+ existsSync as existsSync21,
13609
14415
  lstatSync as lstatSync5,
13610
- mkdirSync as mkdirSync15,
14416
+ mkdirSync as mkdirSync16,
13611
14417
  readFileSync as readFileSync17,
13612
- renameSync as renameSync12,
14418
+ renameSync as renameSync13,
13613
14419
  rmSync as rmSync11,
13614
- writeFileSync as writeFileSync15
14420
+ writeFileSync as writeFileSync16
13615
14421
  } from "fs";
13616
- import { dirname as dirname10 } from "path";
14422
+ import { dirname as dirname12 } from "path";
13617
14423
  import { fileURLToPath as fileURLToPath2 } from "url";
13618
14424
 
13619
14425
  // src/provision.ts
@@ -14546,17 +15352,17 @@ import { randomBytes as randomBytes6 } from "crypto";
14546
15352
  import {
14547
15353
  chmodSync as chmodSync16,
14548
15354
  copyFileSync as copyFileSync3,
14549
- existsSync as existsSync19,
15355
+ existsSync as existsSync20,
14550
15356
  lstatSync as lstatSync4,
14551
- mkdirSync as mkdirSync14,
15357
+ mkdirSync as mkdirSync15,
14552
15358
  readFileSync as readFileSync16,
14553
15359
  realpathSync as realpathSync6,
14554
- renameSync as renameSync11,
15360
+ renameSync as renameSync12,
14555
15361
  rmSync as rmSync10,
14556
15362
  symlinkSync as symlinkSync5,
14557
- writeFileSync as writeFileSync14
15363
+ writeFileSync as writeFileSync15
14558
15364
  } from "fs";
14559
- import { dirname as dirname9 } from "path";
15365
+ import { dirname as dirname11 } from "path";
14560
15366
  async function readCapabilities(run2) {
14561
15367
  const answers = {};
14562
15368
  const checks = Object.entries(CAPABILITY_CHECKS);
@@ -14591,10 +15397,10 @@ var fixed = async (argv2, stdin) => {
14591
15397
  var runProvisionOperation = async (operation) => {
14592
15398
  if (operation.kind === "commands") {
14593
15399
  let output = "";
14594
- for (const command2 of operation.commands) {
14595
- const result = await fixed(command2.argv);
15400
+ for (const command3 of operation.commands) {
15401
+ const result = await fixed(command3.argv);
14596
15402
  output += result.stdout;
14597
- if (!(command2.acceptedExitCodes ?? [0]).includes(result.exitCode))
15403
+ if (!(command3.acceptedExitCodes ?? [0]).includes(result.exitCode))
14598
15404
  return { stdout: output, exitCode: result.exitCode };
14599
15405
  }
14600
15406
  return { stdout: output, exitCode: 0 };
@@ -14615,19 +15421,19 @@ var runProvisionOperation = async (operation) => {
14615
15421
  }
14616
15422
  if (operation.kind === "install-runtime") {
14617
15423
  const release = `/opt/forgezero/agent/versions/${operation.version}`;
14618
- mkdirSync14(`${release}/dist`, { recursive: true, mode: 493 });
14619
- mkdirSync14(dirname9(operation.binary), { recursive: true, mode: 493 });
15424
+ mkdirSync15(`${release}/dist`, { recursive: true, mode: 493 });
15425
+ mkdirSync15(dirname11(operation.binary), { recursive: true, mode: 493 });
14620
15426
  copyFileSync3(operation.source, `${release}/dist/fz-agent.js`);
14621
15427
  chmodSync16(`${release}/dist/fz-agent.js`, 493);
14622
- const gitSshSource = `${dirname9(operation.source)}/fz-git-ssh.js`;
14623
- if (!existsSync19(gitSshSource))
15428
+ const gitSshSource = `${dirname11(operation.source)}/fz-git-ssh.js`;
15429
+ if (!existsSync20(gitSshSource))
14624
15430
  return { stdout: "packaged fz-git-ssh.js is missing", exitCode: 1 };
14625
15431
  copyFileSync3(gitSshSource, `${release}/dist/fz-git-ssh.js`);
14626
15432
  chmodSync16(`${release}/dist/fz-git-ssh.js`, 493);
14627
15433
  const pending = "/opt/forgezero/agent/current.next";
14628
15434
  rmSync10(pending, { force: true });
14629
15435
  symlinkSync5(`versions/${operation.version}`, pending);
14630
- renameSync11(pending, "/opt/forgezero/agent/current");
15436
+ renameSync12(pending, "/opt/forgezero/agent/current");
14631
15437
  rmSync10(operation.binary, { force: true });
14632
15438
  symlinkSync5("/opt/forgezero/agent/current/dist/fz-agent.js", operation.binary);
14633
15439
  const gitSshBinary = "/usr/local/lib/forgezero/agent/fz-git-ssh";
@@ -14636,7 +15442,7 @@ var runProvisionOperation = async (operation) => {
14636
15442
  return { stdout: "", exitCode: 0 };
14637
15443
  }
14638
15444
  if (operation.kind === "ensure-seed") {
14639
- if (existsSync19(operation.credential) && lstatSync4(operation.credential).size > 0)
15445
+ if (existsSync20(operation.credential) && lstatSync4(operation.credential).size > 0)
14640
15446
  return { stdout: "", exitCode: 0 };
14641
15447
  const seed = randomBytes6(32).toString("base64url");
14642
15448
  const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=agent-seed", "-", operation.credential], seed);
@@ -14648,7 +15454,7 @@ var runProvisionOperation = async (operation) => {
14648
15454
  const key = "/run/forgezero-git-deploy-key";
14649
15455
  const publicKey = `${key}.pub`;
14650
15456
  try {
14651
- if (!existsSync19(operation.credential) || lstatSync4(operation.credential).size < 1) {
15457
+ if (!existsSync20(operation.credential) || lstatSync4(operation.credential).size < 1) {
14652
15458
  rmSync10(key, { force: true });
14653
15459
  rmSync10(publicKey, { force: true });
14654
15460
  let result = await fixed(["/usr/bin/ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "forgezero-compute", "-f", key]);
@@ -14659,8 +15465,8 @@ var runProvisionOperation = async (operation) => {
14659
15465
  return result;
14660
15466
  chmodSync16(operation.credential, 256);
14661
15467
  }
14662
- if (!existsSync19(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
14663
- if (!existsSync19(key)) {
15468
+ if (!existsSync20(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
15469
+ if (!existsSync20(key)) {
14664
15470
  const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=git-deploy-key", operation.credential, key]);
14665
15471
  if (decrypted.exitCode !== 0)
14666
15472
  return decrypted;
@@ -14668,7 +15474,7 @@ var runProvisionOperation = async (operation) => {
14668
15474
  const derived = await fixed(["/usr/bin/ssh-keygen", "-y", "-f", key]);
14669
15475
  if (derived.exitCode !== 0)
14670
15476
  return derived;
14671
- writeFileSync14(operation.publicKey, `${derived.stdout.trim()} forgezero-compute
15477
+ writeFileSync15(operation.publicKey, `${derived.stdout.trim()} forgezero-compute
14672
15478
  `, { mode: 292 });
14673
15479
  }
14674
15480
  return { stdout: "", exitCode: 0 };
@@ -14681,12 +15487,12 @@ var runProvisionOperation = async (operation) => {
14681
15487
  const key = "/run/forgezero-bootstrap-ssh-key";
14682
15488
  const generatedPublicKey = `${key}.pub`;
14683
15489
  try {
14684
- if (!existsSync19(operation.credential) || lstatSync4(operation.credential).size < 1) {
15490
+ if (!existsSync20(operation.credential) || lstatSync4(operation.credential).size < 1) {
14685
15491
  rmSync10(key, { force: true });
14686
15492
  rmSync10(generatedPublicKey, { force: true });
14687
15493
  let result;
14688
15494
  if (operation.source) {
14689
- const source = existsSync19(operation.source) ? lstatSync4(operation.source) : undefined;
15495
+ const source = existsSync20(operation.source) ? lstatSync4(operation.source) : undefined;
14690
15496
  if (!source?.isFile() || source.isSymbolicLink() || source.uid !== 0 || source.nlink !== 1 || (source.mode & 63) !== 0 || source.size < 32 || source.size > 16 * 1024) {
14691
15497
  return { stdout: "bootstrap SSH private-key source is missing or unsafe", exitCode: 1 };
14692
15498
  }
@@ -14706,8 +15512,8 @@ var runProvisionOperation = async (operation) => {
14706
15512
  return result;
14707
15513
  chmodSync16(operation.credential, 256);
14708
15514
  }
14709
- if (!existsSync19(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
14710
- if (!existsSync19(key)) {
15515
+ if (!existsSync20(operation.publicKey) || lstatSync4(operation.publicKey).size < 1) {
15516
+ if (!existsSync20(key)) {
14711
15517
  const decrypted = await fixed(["/usr/bin/systemd-creds", "decrypt", "--name=bootstrap-ssh-key", operation.credential, key]);
14712
15518
  if (decrypted.exitCode !== 0)
14713
15519
  return decrypted;
@@ -14717,8 +15523,8 @@ var runProvisionOperation = async (operation) => {
14717
15523
  if (derived.exitCode !== 0 || !/^ssh-ed25519 [A-Za-z0-9+/]+={0,3}\s*$/.test(derived.stdout)) {
14718
15524
  return { stdout: "bootstrap SSH public key derivation failed", exitCode: 1 };
14719
15525
  }
14720
- mkdirSync14(dirname9(operation.publicKey), { recursive: true, mode: 493 });
14721
- writeFileSync14(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
15526
+ mkdirSync15(dirname11(operation.publicKey), { recursive: true, mode: 493 });
15527
+ writeFileSync15(operation.publicKey, `${derived.stdout.trim()} forgezero-bootstrap-runner
14722
15528
  `, { mode: 292 });
14723
15529
  chmodSync16(operation.publicKey, 292);
14724
15530
  }
@@ -14731,9 +15537,9 @@ var runProvisionOperation = async (operation) => {
14731
15537
  }
14732
15538
  }
14733
15539
  if (operation.kind === "ensure-enrolment") {
14734
- if (existsSync19(operation.state) && lstatSync4(operation.state).size > 0 || existsSync19(operation.credential) && lstatSync4(operation.credential).size > 0)
15540
+ if (existsSync20(operation.state) && lstatSync4(operation.state).size > 0 || existsSync20(operation.credential) && lstatSync4(operation.credential).size > 0)
14735
15541
  return { stdout: "", exitCode: 0 };
14736
- if (!existsSync19(operation.source))
15542
+ if (!existsSync20(operation.source))
14737
15543
  return { stdout: "enrolment source is missing", exitCode: 1 };
14738
15544
  const result = await fixed(["/usr/bin/systemd-creds", "encrypt", "--name=enrol-token", operation.source, operation.credential]);
14739
15545
  if (result.exitCode === 0) {
@@ -14753,7 +15559,7 @@ var runProvisionOperation = async (operation) => {
14753
15559
  return { stdout: `socket did not become ready: ${operation.path}`, exitCode: 1 };
14754
15560
  }
14755
15561
  if (operation.kind === "verify-file")
14756
- return existsSync19(operation.path) && lstatSync4(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
15562
+ return existsSync20(operation.path) && lstatSync4(operation.path).size > 0 ? { stdout: "", exitCode: 0 } : { stdout: "required file is empty", exitCode: 1 };
14757
15563
  if (operation.kind === "verify-egress") {
14758
15564
  const active = await fixed(["/usr/bin/systemctl", "is-active", "forgezero-agent-egress.service"]);
14759
15565
  if (active.exitCode !== 0)
@@ -14781,11 +15587,11 @@ var runProvisionOperation = async (operation) => {
14781
15587
  const response = await fetch("https://pkg.cloudflareclient.com/pubkey.gpg", { signal: AbortSignal.timeout(30000) });
14782
15588
  if (!response.ok)
14783
15589
  return { stdout: `WARP key HTTP ${response.status}`, exitCode: 1 };
14784
- mkdirSync14("/usr/share/keyrings", { recursive: true, mode: 493 });
14785
- mkdirSync14("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
14786
- mkdirSync14("/etc/systemd/system/warp-svc.service.d", { recursive: true, mode: 493 });
15590
+ mkdirSync15("/usr/share/keyrings", { recursive: true, mode: 493 });
15591
+ mkdirSync15("/etc/apt/sources.list.d", { recursive: true, mode: 493 });
15592
+ mkdirSync15("/etc/systemd/system/warp-svc.service.d", { recursive: true, mode: 493 });
14787
15593
  const key = "/run/cloudflare-warp-key.gpg";
14788
- writeFileSync14(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
15594
+ writeFileSync15(key, new Uint8Array(await response.arrayBuffer()), { mode: 384 });
14789
15595
  let result = await fixed(["/usr/bin/gpg", "--batch", "--yes", "--dearmor", "-o", "/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg", key]);
14790
15596
  rmSync10(key, { force: true });
14791
15597
  if (result.exitCode !== 0)
@@ -14793,7 +15599,7 @@ var runProvisionOperation = async (operation) => {
14793
15599
  const codename = os.match(/^VERSION_CODENAME=(.+)$/m)?.[1]?.replace(/^"|"$/g, "");
14794
15600
  if (!codename)
14795
15601
  return { stdout: "Ubuntu codename missing", exitCode: 1 };
14796
- writeFileSync14("/etc/apt/sources.list.d/cloudflare-client.list", `deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ ${codename} main
15602
+ writeFileSync15("/etc/apt/sources.list.d/cloudflare-client.list", `deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ ${codename} main
14797
15603
  `, { mode: 420 });
14798
15604
  result = await fixed(["/usr/bin/apt-get", "update", "-qq"]);
14799
15605
  return result.exitCode === 0 ? fixed(["/usr/bin/apt-get", "install", "-y", "cloudflare-warp"]) : result;
@@ -15107,16 +15913,16 @@ function localBootstrapHost() {
15107
15913
  };
15108
15914
  return {
15109
15915
  uid: () => process.getuid?.() ?? -1,
15110
- exists: existsSync20,
15916
+ exists: existsSync21,
15111
15917
  read: (path2) => readFileSync17(path2, "utf8"),
15112
15918
  write(path2, content, mode) {
15113
- mkdirSync15(dirname10(path2), { recursive: true, mode: 493 });
15919
+ mkdirSync16(dirname12(path2), { recursive: true, mode: 493 });
15114
15920
  const temporary = `${path2}.next.${process.pid}`;
15115
- writeFileSync15(temporary, content, { mode });
15921
+ writeFileSync16(temporary, content, { mode });
15116
15922
  chmodSync17(temporary, mode);
15117
- renameSync12(temporary, path2);
15923
+ renameSync13(temporary, path2);
15118
15924
  },
15119
- mkdir: (path2, mode) => mkdirSync15(path2, { recursive: true, mode }),
15925
+ mkdir: (path2, mode) => mkdirSync16(path2, { recursive: true, mode }),
15120
15926
  remove: (path2) => rmSync11(path2, { force: true }),
15121
15927
  inspect(path2) {
15122
15928
  const value = lstatSync5(path2);
@@ -15140,9 +15946,9 @@ function localBootstrapHost() {
15140
15946
  },
15141
15947
  async installAgent(config, phase) {
15142
15948
  const capabilities = await readCapabilities(localRunner);
15143
- const hasBinding = existsSync20("/var/lib/forgezero/enrolment.json");
15144
- const hasEnrolCredential = existsSync20(ENROL_CREDENTIAL);
15145
- const initialBundle = config.kind === "platform" && !existsSync20(BOOTSTRAP_RELEASE_EVIDENCE) ? {
15949
+ const hasBinding = existsSync21("/var/lib/forgezero/enrolment.json");
15950
+ const hasEnrolCredential = existsSync21(ENROL_CREDENTIAL);
15951
+ const initialBundle = config.kind === "platform" && !existsSync21(BOOTSTRAP_RELEASE_EVIDENCE) ? {
15146
15952
  path: config.bootstrapBundle.bundleFile,
15147
15953
  manifestPath: config.bootstrapBundle.manifestFile,
15148
15954
  manifest: parseBootstrapBundleManifest(JSON.parse(readFileSync17(config.bootstrapBundle.manifestFile, "utf8")))
@@ -15158,8 +15964,8 @@ function localBootstrapHost() {
15158
15964
  databaseHealthUrl: `http://${config.database.address}:8529/_api/version`,
15159
15965
  databasePorts: [8529]
15160
15966
  };
15161
- mkdirSync15(dirname10(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
15162
- writeFileSync15(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
15967
+ mkdirSync16(dirname12(LIFECYCLE_PROFILE), { recursive: true, mode: 493 });
15968
+ writeFileSync16(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
15163
15969
  `, { mode: 256 });
15164
15970
  }
15165
15971
  const plan = planBootstrapAgentInstall(config, phase, {
@@ -15169,8 +15975,8 @@ function localBootstrapHost() {
15169
15975
  initialBundle
15170
15976
  });
15171
15977
  for (const unit3 of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
15172
- mkdirSync15(dirname10(unit3.path), { recursive: true, mode: 493 });
15173
- writeFileSync15(unit3.path, unit3.unit, { mode: 420 });
15978
+ mkdirSync16(dirname12(unit3.path), { recursive: true, mode: 493 });
15979
+ writeFileSync16(unit3.path, unit3.unit, { mode: 420 });
15174
15980
  }
15175
15981
  await applyPlan(plan, localRunner);
15176
15982
  return plan;
@@ -15222,10 +16028,10 @@ async function verifyPlatformFleetHost(expectedVersion, runtime = localRuntime()
15222
16028
  }
15223
16029
 
15224
16030
  // src/community-rehearsal-host.ts
15225
- import { createHash as createHash11 } from "crypto";
15226
- import { lstatSync as lstatSync7, mkdirSync as mkdirSync16, readFileSync as readFileSync18, rmSync as rmSync12, symlinkSync as symlinkSync6, unlinkSync as unlinkSync12, writeFileSync as writeFileSync16 } from "fs";
16031
+ import { createHash as createHash12 } from "crypto";
16032
+ import { lstatSync as lstatSync7, mkdirSync as mkdirSync17, readFileSync as readFileSync18, rmSync as rmSync12, symlinkSync as symlinkSync6, unlinkSync as unlinkSync12, writeFileSync as writeFileSync17 } from "fs";
15227
16033
  import { isIP as isIP5 } from "net";
15228
- import { dirname as dirname11 } from "path";
16034
+ import { dirname as dirname13 } from "path";
15229
16035
  var COMMUNITY_REHEARSAL_VERSION = "3.11.14";
15230
16036
  var COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/community-cluster-api";
15231
16037
  var COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
@@ -15443,8 +16249,8 @@ async function applyOperations(operations) {
15443
16249
  if (operation.kind === "remove-tree")
15444
16250
  rmSync12(operation.path, { recursive: true, force: true });
15445
16251
  else if (operation.kind === "write") {
15446
- mkdirSync16(dirname11(operation.path), { recursive: true });
15447
- writeFileSync16(operation.path, operation.content, { mode: operation.mode });
16252
+ mkdirSync17(dirname13(operation.path), { recursive: true });
16253
+ writeFileSync17(operation.path, operation.content, { mode: operation.mode });
15448
16254
  } else if (operation.kind === "unlink") {
15449
16255
  try {
15450
16256
  unlinkSync12(operation.path);
@@ -15496,7 +16302,7 @@ async function runCommunityRehearsalHost(request) {
15496
16302
  }
15497
16303
  if (request.action === "prepare") {
15498
16304
  const metadata = lstatSync7(request.archivePath);
15499
- if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash11("sha256").update(readFileSync18(request.archivePath)).digest("hex") !== request.archiveSha256) {
16305
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size < 1 || metadata.size > 64 * 1024 * 1024 || createHash12("sha256").update(readFileSync18(request.archivePath)).digest("hex") !== request.archiveSha256) {
15500
16306
  throw new Error("community rehearsal archive is not the declared bounded release");
15501
16307
  }
15502
16308
  await ensureCommunityRehearsalArango();
@@ -15590,23 +16396,23 @@ async function runSupervisedApp(path2) {
15590
16396
  }
15591
16397
 
15592
16398
  // src/index.ts
15593
- function agentCommandArgs(args, command2) {
15594
- const index = args.indexOf(command2);
16399
+ function agentCommandArgs(args, command3) {
16400
+ const index = args.indexOf(command3);
15595
16401
  if (index < 0)
15596
- throw new Error(`missing Agent command ${command2}`);
16402
+ throw new Error(`missing Agent command ${command3}`);
15597
16403
  return args.slice(index + 1);
15598
16404
  }
15599
16405
  function loadOrCreateSeed(path2) {
15600
- if (existsSync21(path2)) {
16406
+ if (existsSync22(path2)) {
15601
16407
  const seed2 = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
15602
16408
  if (seed2.length < 32) {
15603
16409
  throw new Error(`agent: the seed at ${path2} is too short to derive a key from.`);
15604
16410
  }
15605
16411
  return seed2;
15606
16412
  }
15607
- mkdirSync17(dirname12(path2), { recursive: true });
16413
+ mkdirSync18(dirname14(path2), { recursive: true });
15608
16414
  const seed = new Uint8Array(randomBytes7(32));
15609
- writeFileSync17(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
16415
+ writeFileSync18(path2, Buffer.from(seed).toString("base64url"), { mode: 384 });
15610
16416
  chmodSync18(path2, 384);
15611
16417
  return seed;
15612
16418
  }
@@ -15636,7 +16442,7 @@ function loadSeedCredential(name = DEFAULT_SEED_CREDENTIAL, directory = process.
15636
16442
  if (!directory)
15637
16443
  throw new Error("agent: CREDENTIALS_DIRECTORY is missing; systemd did not load the node identity.");
15638
16444
  const path2 = `${directory}/${name}`;
15639
- if (!existsSync21(path2))
16445
+ if (!existsSync22(path2))
15640
16446
  throw new Error(`agent: the systemd credential ${name} is missing at ${path2}.`);
15641
16447
  const seed = new Uint8Array(Buffer.from(readFileSync20(path2, "utf8").trim(), "base64url"));
15642
16448
  if (seed.length < 32)
@@ -15658,7 +16464,7 @@ function createSystemdDeploymentSecrets(names, directory = process.env.CREDENTIA
15658
16464
  if (allowed.size === 0)
15659
16465
  return;
15660
16466
  for (const name of allowed) {
15661
- if (!/^[A-Z_][A-Z0-9_]*$/.test(name))
16467
+ if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/.test(name))
15662
16468
  throw new Error(`agent: invalid pipeline credential name ${name}.`);
15663
16469
  }
15664
16470
  return {
@@ -15764,8 +16570,8 @@ if (import.meta.main) {
15764
16570
  console.log(VERSION2);
15765
16571
  process.exit(0);
15766
16572
  }
15767
- const command2 = args.find((arg) => !arg.startsWith("-"));
15768
- if (command2 === "platform-activate") {
16573
+ const command3 = args.find((arg) => !arg.startsWith("-"));
16574
+ if (command3 === "platform-activate") {
15769
16575
  if (process.getuid?.() !== 0)
15770
16576
  throw new Error("platform-activate must run as root");
15771
16577
  const configPath = args.find((arg) => arg.startsWith("--config="))?.slice("--config=".length);
@@ -15778,14 +16584,14 @@ if (import.meta.main) {
15778
16584
  console.log(`promoted ${result.release} on ${result.slot}`);
15779
16585
  process.exit(0);
15780
16586
  }
15781
- if (command2 === "recovery-host") {
16587
+ if (command3 === "recovery-host") {
15782
16588
  if (process.getuid?.() !== 0)
15783
16589
  throw new Error("recovery-host must run as root");
15784
16590
  const commandIndex = args.indexOf("recovery-host");
15785
16591
  await runRecoveryHost(args.slice(commandIndex + 1));
15786
16592
  process.exit(0);
15787
16593
  }
15788
- if (command2 === "pipeline-todo") {
16594
+ if (command3 === "pipeline-todo") {
15789
16595
  const index = args.indexOf("pipeline-todo");
15790
16596
  const message = args[index + 1];
15791
16597
  if (!message || args.length !== index + 2 || message.length > 2000 || /[\r\n\0]/.test(message)) {
@@ -15794,7 +16600,7 @@ if (import.meta.main) {
15794
16600
  console.error(message);
15795
16601
  process.exit(78);
15796
16602
  }
15797
- if (command2 === "project-release-check") {
16603
+ if (command3 === "project-release-check") {
15798
16604
  if (args.length !== 1)
15799
16605
  throw new Error("project-release-check accepts no coordinates");
15800
16606
  for (const relative of ["src/index.ts", "bun.lock", "src/generated/shared/contract-manifest.json"]) {
@@ -15813,7 +16619,7 @@ if (import.meta.main) {
15813
16619
  process.exit(0);
15814
16620
  }
15815
16621
  const allowFileSeed = process.env.FZ_ALLOW_FILE_SEED === "1";
15816
- if (command2 === "identity") {
16622
+ if (command3 === "identity") {
15817
16623
  const seed = configuredAgentSeed({
15818
16624
  credential: process.env.FZ_SEED_CREDENTIAL,
15819
16625
  path: process.env.FZ_SEED_PATH,
@@ -15827,7 +16633,7 @@ if (import.meta.main) {
15827
16633
  }, null, 2));
15828
16634
  process.exit(0);
15829
16635
  }
15830
- if (command2 === "enrol") {
16636
+ if (command3 === "enrol") {
15831
16637
  if (!process.env.FZ_API)
15832
16638
  throw new Error("agent enrolment requires FZ_API");
15833
16639
  if (!process.env.FZ_ENROL_TOKEN_CREDENTIAL) {
@@ -15856,7 +16662,7 @@ if (import.meta.main) {
15856
16662
  console.log(`[agent] enrolled ${binding2.computeReference} in project ${binding2.projectKey}/${binding2.environmentKey}`);
15857
16663
  process.exit(0);
15858
16664
  }
15859
- if (command2 === "egress-policy" || command2 === "egress-policy-check") {
16665
+ if (command3 === "egress-policy" || command3 === "egress-policy-check") {
15860
16666
  const users = args.filter((arg) => arg.startsWith("--user=")).map((arg) => arg.slice("--user=".length));
15861
16667
  if (users.length < 1)
15862
16668
  throw new Error("egress-policy requires --user=<service-user>");
@@ -15875,7 +16681,7 @@ if (import.meta.main) {
15875
16681
  tcpPorts: loopbackTcpPorts,
15876
16682
  publicTcpPorts: publicTcpPorts.length > 0 ? publicTcpPorts : undefined
15877
16683
  } : undefined;
15878
- if (command2 === "egress-policy-check") {
16684
+ if (command3 === "egress-policy-check") {
15879
16685
  const uids = users.map((user) => resolveServiceUid(user));
15880
16686
  const grant = loopback ? {
15881
16687
  uid: resolveServiceUid(loopback.user),
@@ -15888,7 +16694,7 @@ if (import.meta.main) {
15888
16694
  }
15889
16695
  await superviseAgentEgressPolicy({ users, loopback });
15890
16696
  }
15891
- if (command2 === "metal-helper") {
16697
+ if (command3 === "metal-helper") {
15892
16698
  const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
15893
16699
  if (!profilePath)
15894
16700
  throw new Error("metal-helper requires --profile=/absolute/path.json");
@@ -15910,7 +16716,7 @@ if (import.meta.main) {
15910
16716
  process.on("SIGINT", () => void stop());
15911
16717
  await new Promise(() => {});
15912
16718
  }
15913
- if (command2 === "lifecycle-helper") {
16719
+ if (command3 === "lifecycle-helper") {
15914
16720
  const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
15915
16721
  if (!profilePath?.startsWith("/"))
15916
16722
  throw new Error("lifecycle-helper requires --profile=/absolute/path.json");
@@ -15934,7 +16740,7 @@ if (import.meta.main) {
15934
16740
  process.on("SIGINT", () => void stop());
15935
16741
  await new Promise(() => {});
15936
16742
  }
15937
- if (command2 === "warp-config") {
16743
+ if (command3 === "warp-config") {
15938
16744
  const organization = args.find((arg) => arg.startsWith("--organization="))?.slice("--organization=".length);
15939
16745
  if (!organization)
15940
16746
  throw new Error("warp-config requires --organization=<team-name>");
@@ -15952,7 +16758,7 @@ if (import.meta.main) {
15952
16758
  console.log("[warp-config] enrollment materialized in tmpfs");
15953
16759
  process.exit(0);
15954
16760
  }
15955
- if (command2 === "mesh-config") {
16761
+ if (command3 === "mesh-config") {
15956
16762
  if (typeof process.getuid !== "function" || process.getuid() !== 0)
15957
16763
  throw new Error("mesh-config must run as root");
15958
16764
  const token = readMeshConnectorCredential();
@@ -15975,7 +16781,7 @@ if (import.meta.main) {
15975
16781
  console.log("[mesh-config] Cloudflare Mesh connector is connected");
15976
16782
  process.exit(0);
15977
16783
  }
15978
- if (command2 === "update-helper") {
16784
+ if (command3 === "update-helper") {
15979
16785
  if (typeof process.getuid !== "function" || process.getuid() !== 0) {
15980
16786
  throw new Error("update-helper must run as root");
15981
16787
  }
@@ -15994,7 +16800,7 @@ if (import.meta.main) {
15994
16800
  process.on("SIGINT", () => void stop());
15995
16801
  await new Promise(() => {});
15996
16802
  }
15997
- if (command2 === "software-helper") {
16803
+ if (command3 === "software-helper") {
15998
16804
  if (typeof process.getuid !== "function" || process.getuid() !== 0) {
15999
16805
  throw new Error("software-helper must run as root");
16000
16806
  }
@@ -16016,13 +16822,13 @@ if (import.meta.main) {
16016
16822
  process.on("SIGINT", () => void stop());
16017
16823
  await new Promise(() => {});
16018
16824
  }
16019
- if (command2 === "supervised-app") {
16825
+ if (command3 === "supervised-app") {
16020
16826
  const config = args.find((arg) => arg.startsWith("--config="))?.slice("--config=".length);
16021
16827
  if (!config)
16022
16828
  throw new Error("supervised-app requires --config=<absolute-path>");
16023
16829
  process.exit(await runSupervisedApp(config));
16024
16830
  }
16025
- if (command2 === "software-ensure") {
16831
+ if (command3 === "software-ensure") {
16026
16832
  const requirements = args.filter((arg) => arg.startsWith("--require=")).map((arg) => {
16027
16833
  const coordinate = arg.slice("--require=".length);
16028
16834
  const separator = coordinate.indexOf("@");
@@ -16038,7 +16844,7 @@ if (import.meta.main) {
16038
16844
  console.log(JSON.stringify({ ok: true, results }));
16039
16845
  process.exit(0);
16040
16846
  }
16041
- if (command2 === "metal-isolation") {
16847
+ if (command3 === "metal-isolation") {
16042
16848
  const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
16043
16849
  if (!profilePath)
16044
16850
  throw new Error("metal-isolation requires --profile=/absolute/path.json");
@@ -16047,7 +16853,7 @@ if (import.meta.main) {
16047
16853
  console.log("[metal-isolation] host and guest cgroup boundaries active");
16048
16854
  process.exit(0);
16049
16855
  }
16050
- if (command2 === "deploy-runner") {
16856
+ if (command3 === "deploy-runner") {
16051
16857
  const root2 = args.find((arg) => arg.startsWith("--root="))?.slice("--root=".length);
16052
16858
  const home = args.find((arg) => arg.startsWith("--home="))?.slice("--home=".length);
16053
16859
  if (!root2 || !home)
@@ -16077,7 +16883,7 @@ if (import.meta.main) {
16077
16883
  process.on("SIGINT", () => void stop());
16078
16884
  await new Promise(() => {});
16079
16885
  }
16080
- if (command2 === "metal-apply") {
16886
+ if (command3 === "metal-apply") {
16081
16887
  const claimArg = args.find((arg) => arg.startsWith("--claim="))?.slice("--claim=".length);
16082
16888
  if (!claimArg)
16083
16889
  throw new Error("metal-apply requires --claim=- or --claim=/absolute/path.json");
@@ -16098,8 +16904,8 @@ if (import.meta.main) {
16098
16904
  console.log(JSON.stringify({ ok: true, computeKey: claim.computeKey, ...result }));
16099
16905
  process.exit(0);
16100
16906
  }
16101
- if (command2 === "platform-fleet-verify") {
16102
- const commandArgs = agentCommandArgs(args, command2);
16907
+ if (command3 === "platform-fleet-verify") {
16908
+ const commandArgs = agentCommandArgs(args, command3);
16103
16909
  const expectedVersion = commandArgs.find((arg) => arg.startsWith("--expected-version="))?.slice("--expected-version=".length);
16104
16910
  if (!expectedVersion || commandArgs.length !== 1) {
16105
16911
  throw new Error("platform-fleet-verify requires exactly --expected-version=<semver>");
@@ -16107,8 +16913,8 @@ if (import.meta.main) {
16107
16913
  console.log(JSON.stringify(await verifyPlatformFleetHost(expectedVersion)));
16108
16914
  process.exit(0);
16109
16915
  }
16110
- if (command2 === "community-rehearsal") {
16111
- const commandArgs = agentCommandArgs(args, command2);
16916
+ if (command3 === "community-rehearsal") {
16917
+ const commandArgs = agentCommandArgs(args, command3);
16112
16918
  if (commandArgs.length !== 1 || commandArgs[0] !== "--request=-") {
16113
16919
  throw new Error("community-rehearsal requires exactly --request=-");
16114
16920
  }
@@ -16119,7 +16925,7 @@ if (import.meta.main) {
16119
16925
  console.log(JSON.stringify(await runCommunityRehearsalHost(request)));
16120
16926
  process.exit(0);
16121
16927
  }
16122
- if (command2 === "metal-admission-proof") {
16928
+ if (command3 === "metal-admission-proof") {
16123
16929
  if (args.length !== 1 || args[0] !== "--request=-") {
16124
16930
  throw new Error("metal-admission-proof requires exactly --request=-");
16125
16931
  }
@@ -16133,17 +16939,17 @@ if (import.meta.main) {
16133
16939
  const seed = configuredAgentSeed({ credential: process.env.FZ_SEED_CREDENTIAL, allowFileSeed: false });
16134
16940
  const keys2 = deriveKeysFromSeed(seed);
16135
16941
  seed.fill(0);
16136
- const filesystem = statfsSync2("/var/lib/forgezero");
16942
+ const filesystem = statfsSync3("/var/lib/forgezero");
16137
16943
  const body = {
16138
16944
  ...request,
16139
16945
  publicKeys: { ed25519: keys2.ed25519.publicKey, mlDsa: keys2.mlDsa.publicKey },
16140
16946
  preflight: {
16141
- vcpu: cpus().length,
16142
- memoryGib: Math.max(1, Math.floor(totalmem() / 1024 ** 3)),
16947
+ vcpu: cpus2().length,
16948
+ memoryGib: Math.max(1, Math.floor(totalmem2() / 1024 ** 3)),
16143
16949
  diskGib: Math.max(1, Math.floor(Number(filesystem.blocks) * Number(filesystem.bsize) / 1024 ** 3)),
16144
- kvm: existsSync21("/dev/kvm"),
16145
- snpHost: existsSync21("/dev/sev"),
16146
- helper: existsSync21(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
16950
+ kvm: existsSync22("/dev/kvm"),
16951
+ snpHost: existsSync22("/dev/sev"),
16952
+ helper: existsSync22(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
16147
16953
  }
16148
16954
  };
16149
16955
  const envelope = signRequest(keys2, keys2.ed25519.publicKey, {
@@ -16178,9 +16984,9 @@ if (import.meta.main) {
16178
16984
  metalHostname: process.env.FZ_METAL_HOSTNAME,
16179
16985
  run: (claim) => requestMetalProvision(claim, process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET),
16180
16986
  metalPreflight: () => ({
16181
- snpHost: existsSync21("/dev/sev"),
16182
- kvm: existsSync21("/dev/kvm"),
16183
- helper: existsSync21(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
16987
+ snpHost: existsSync22("/dev/sev"),
16988
+ kvm: existsSync22("/dev/kvm"),
16989
+ helper: existsSync22(process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET)
16184
16990
  }),
16185
16991
  onEvent: (event, detail) => console.log(`[metal-agent] ${event}${detail ? ` ${JSON.stringify(detail)}` : ""}`)
16186
16992
  });
@@ -16235,22 +17041,22 @@ if (import.meta.main) {
16235
17041
  process.on("SIGINT", () => void stop("SIGINT"));
16236
17042
  await new Promise(() => {});
16237
17043
  }
16238
- if (command2 && ["deploy", "status", "pause", "resume", "pause-key", "resume-key", "stop-key", "start-key", "cancel"].includes(command2)) {
17044
+ if (command3 && ["deploy", "status", "pause", "resume", "pause-key", "resume-key", "stop-key", "start-key", "cancel"].includes(command3)) {
16239
17045
  const revisionArg = args.find((arg) => arg.startsWith("--revision="));
16240
17046
  const socketArg = args.find((arg) => arg.startsWith("--control-socket="));
16241
17047
  const keyArg = args.find((arg) => arg.startsWith("--key="));
16242
17048
  const idArg = args.find((arg) => arg.startsWith("--id="));
16243
17049
  const socketPath = socketArg?.slice("--control-socket=".length) ?? process.env.FZ_CONTROL_SOCKET ?? DEFAULT_CONTROL_SOCKET;
16244
- const request = command2 === "deploy" ? {
17050
+ const request = command3 === "deploy" ? {
16245
17051
  op: "deploy",
16246
17052
  request: {
16247
17053
  revision: revisionArg?.slice("--revision=".length),
16248
17054
  releaseExecutor: args.includes("--release-executor")
16249
17055
  }
16250
- } : command2 === "cancel" ? { op: "cancel", id: idArg?.slice("--id=".length) ?? "" } : ["pause-key", "resume-key", "stop-key", "start-key"].includes(command2) ? {
16251
- op: command2,
17056
+ } : command3 === "cancel" ? { op: "cancel", id: idArg?.slice("--id=".length) ?? "" } : ["pause-key", "resume-key", "stop-key", "start-key"].includes(command3) ? {
17057
+ op: command3,
16252
17058
  key: keyArg?.slice("--key=".length) ?? ""
16253
- } : { op: command2 };
17059
+ } : { op: command3 };
16254
17060
  try {
16255
17061
  const response = await requestControl(request, socketPath);
16256
17062
  console.log(JSON.stringify(response, null, 2));
@@ -16263,7 +17069,7 @@ if (import.meta.main) {
16263
17069
  const telemetry = new AgentTelemetryRuntime(createAgentTelemetry(resolveAgentTelemetryConfig()));
16264
17070
  telemetry.event("agent.started");
16265
17071
  telemetry.setDraining(false);
16266
- const attestationSource = existsSync21("/dev/sev-guest") ? createSnpAttestationSource() : undefined;
17072
+ const attestationSource = existsSync22("/dev/sev-guest") ? createSnpAttestationSource() : undefined;
16267
17073
  const running = runAgent({
16268
17074
  socketPath: process.env.FZ_SOCKET_PATH ?? DEFAULT_SOCKET_PATH,
16269
17075
  seedCredential: process.env.FZ_SEED_CREDENTIAL,
@@ -16278,8 +17084,8 @@ if (import.meta.main) {
16278
17084
  const enrolmentStatePath = process.env.FZ_ENROL_STATE_FILE ?? DEFAULT_ENROLMENT_STATE_PATH;
16279
17085
  let binding = loadGuestBinding(enrolmentStatePath, nodeKey);
16280
17086
  const enrolmentCredential = process.env.FZ_ENROL_TOKEN_CREDENTIAL;
16281
- const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY && existsSync21(`${process.env.CREDENTIALS_DIRECTORY}/${enrolmentCredential}`));
16282
- const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE && existsSync21(process.env.FZ_ENROL_TOKEN_FILE));
17087
+ const enrolmentCredentialAvailable = Boolean(enrolmentCredential && process.env.CREDENTIALS_DIRECTORY && existsSync22(`${process.env.CREDENTIALS_DIRECTORY}/${enrolmentCredential}`));
17088
+ const legacyEnrolmentFileAvailable = Boolean(process.env.FZ_ENROL_TOKEN_FILE && existsSync22(process.env.FZ_ENROL_TOKEN_FILE));
16283
17089
  if (!binding && (enrolmentCredentialAvailable || legacyEnrolmentFileAvailable) && process.env.FZ_API) {
16284
17090
  binding = await enrolGuestIdentity({
16285
17091
  apiUrl: process.env.FZ_API,
@@ -16357,7 +17163,7 @@ if (import.meta.main) {
16357
17163
  const bootstrapEnabled = process.env.FZ_BOOTSTRAP_PULL === "true";
16358
17164
  const bootstrapCredential = process.env.FZ_BOOTSTRAP_SSH_KEY_CREDENTIAL;
16359
17165
  const bootstrapKeyPath = bootstrapCredential && process.env.CREDENTIALS_DIRECTORY ? `${process.env.CREDENTIALS_DIRECTORY}/${bootstrapCredential}` : undefined;
16360
- if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !existsSync21(bootstrapKeyPath) || !process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT)) {
17166
+ if (bootstrapEnabled && (!binding || !nodeApiUrl || !process.env.FZ_API || !bootstrapKeyPath || !existsSync22(bootstrapKeyPath) || !process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT)) {
16361
17167
  throw new Error("agent: bootstrap runner requires enrolment, API, local SSH credential and target OTLP coordinate");
16362
17168
  }
16363
17169
  const bootstrapPull = bootstrapEnabled ? startSshBootstrapPull({
@@ -16433,6 +17239,7 @@ if (import.meta.main) {
16433
17239
  activateService: (request) => requestServiceActivation(request, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
16434
17240
  buildContainer: (request) => requestContainerBuild(request, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
16435
17241
  activateContainer: (request) => requestContainerActivation(request, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
17242
+ applyConnectivity: (request) => requestDeploymentConnectivity(request, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
16436
17243
  projectExec: process.env.FZ_DEPLOY_RUNNER_SOCKET ? (input) => requestDeploymentCommand(input, process.env.FZ_DEPLOY_RUNNER_SOCKET) : undefined
16437
17244
  });
16438
17245
  const staticManager = repository && branch && profile ? createDeploymentManager(managerOptions({ repository, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `${repository}:${branch}:${profile}`)) : bootstrapBundle && branch && profile ? createDeploymentManager({
@@ -16650,6 +17457,8 @@ export {
16650
17457
  selectAgentRelease,
16651
17458
  runAgent,
16652
17459
  restoreAgentRelease,
17460
+ resolveDeploymentTargets,
17461
+ resolveDeploymentInputs,
16653
17462
  resolveAgentTelemetryConfig,
16654
17463
  requestSoftware,
16655
17464
  requestMetalProvision,
@@ -16727,6 +17536,7 @@ export {
16727
17536
  OS_CATALOG,
16728
17537
  METAL_SYSTEMD_CREDENTIALS,
16729
17538
  MAX_AGENT_TARBALL_BYTES,
17539
+ DeploymentInputError,
16730
17540
  DeploymentError,
16731
17541
  DEFAULT_SOFTWARE_HELPER_SOCKET,
16732
17542
  DEFAULT_SOCKET_PATH,