@forgezero/agent 0.1.65 → 0.1.67

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.
package/dist/fz.js CHANGED
@@ -4634,7 +4634,7 @@ var VaultError, runtimeEnvironment = () => typeof process !== "undefined" && pro
4634
4634
  } catch {
4635
4635
  return;
4636
4636
  }
4637
- }, VERSION = "0.1.12";
4637
+ }, VERSION = "0.1.13";
4638
4638
  var init_dist = __esm(() => {
4639
4639
  init_identity();
4640
4640
  VaultError = class VaultError extends Error {
@@ -4829,13 +4829,36 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
4829
4829
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
4830
4830
 
4831
4831
  // src/version.ts
4832
- var VERSION2 = "0.1.65";
4832
+ var VERSION2 = "0.1.67";
4833
4833
 
4834
4834
  // src/software.ts
4835
4835
  var PINNED_BUN_VERSION = "1.3.14";
4836
4836
  var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
4837
4837
  var OS_CATALOG = [
4838
- { id: "ubuntu", version: "26.04", architecture: "x64", status: "active" }
4838
+ {
4839
+ id: "ubuntu",
4840
+ version: "26.04",
4841
+ architecture: "x64",
4842
+ status: "active",
4843
+ imageKey: "ubuntu-resolute-20260731",
4844
+ imageLabel: "Ubuntu 26.04 LTS Resolute",
4845
+ imageVersion: "2026-07-31",
4846
+ imageUrl: "https://cloud-images.ubuntu.com/releases/resolute/release-20260731/ubuntu-26.04-server-cloudimg-amd64.img",
4847
+ imageSha256: "9dc7c5363c0146a08ba0c9aa834d82c2c6dfbb1c471ad9a2f0aba1189e21be05",
4848
+ confidentialModes: ["sev-snp"]
4849
+ },
4850
+ {
4851
+ id: "ubuntu",
4852
+ version: "24.04",
4853
+ architecture: "x64",
4854
+ status: "active",
4855
+ imageKey: "ubuntu-noble-20260705",
4856
+ imageLabel: "Ubuntu 24.04 LTS Noble",
4857
+ imageVersion: "2026-07-05",
4858
+ imageUrl: "https://cloud-images.ubuntu.com/releases/noble/release-20260705/ubuntu-24.04-server-cloudimg-amd64.img",
4859
+ imageSha256: "ffe6203da54deeb6db5d2a98a83f9ec8e55f149d3f7ba622e1abe5fa966ee3d6",
4860
+ confidentialModes: []
4861
+ }
4839
4862
  ];
4840
4863
  var SOFTWARE_CATALOG = [
4841
4864
  { id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
@@ -4846,7 +4869,12 @@ var SOFTWARE_CATALOG = [
4846
4869
  { id: "cloudflare-warp", version: "2026.6.822.0-min", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4847
4870
  { id: "ufw", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4848
4871
  { id: "openssh-client", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4849
- { id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
4872
+ { id: "git", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4873
+ { id: "docker", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4874
+ { id: "nginx", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4875
+ { id: "ufw", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4876
+ { id: "openssh-client", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4877
+ { id: "git", version: "ubuntu-24.04", status: "active", os: "ubuntu", osVersion: "24.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
4850
4878
  ];
4851
4879
  var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
4852
4880
  "data-root": "/var/lib/docker",
@@ -10425,13 +10453,13 @@ function validateBuiltInProvider(requirement2, where) {
10425
10453
  return;
10426
10454
  const expected = {
10427
10455
  "forgezero.bun": { capability: "runtime.javascript", versions: ["1.3.14"], keys: ["installScope"] },
10428
- "forgezero.docker": { capability: "runtime.container", versions: ["ubuntu-26.04"], keys: ["installScope", "storageDriver", "dataRoot", "liveRestore", "logDriver", "defaultNetwork", "rootless"] },
10429
- "forgezero.nginx": { capability: "proxy.http", versions: ["ubuntu-26.04"], keys: ["installScope", "websocket", "maximumBodyMiB", "workerProcesses", "workerConnections"] },
10456
+ "forgezero.docker": { capability: "runtime.container", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "storageDriver", "dataRoot", "liveRestore", "logDriver", "defaultNetwork", "rootless"] },
10457
+ "forgezero.nginx": { capability: "proxy.http", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "websocket", "maximumBodyMiB", "workerProcesses", "workerConnections"] },
10430
10458
  "forgezero.arangodb": { capability: "database.arangodb", versions: ["3.11.14"], keys: ["installScope", "runtime", "dataPath", "bind"] },
10431
10459
  "forgezero.cloudflared": { capability: "network.public-tunnel", versions: ["2026.7.3"], keys: ["installScope", "mode"] },
10432
10460
  "forgezero.warp": { capability: "network.private", versions: ["2026.6.822.0-min"], keys: ["installScope", "mode"] },
10433
- "forgezero.ufw": { capability: "security.firewall", versions: ["ubuntu-26.04"], keys: ["installScope", "defaultIncoming", "defaultOutgoing"] },
10434
- "forgezero.openssh": { capability: "transport.ssh", versions: ["ubuntu-26.04"], keys: ["installScope", "mode"] },
10461
+ "forgezero.ufw": { capability: "security.firewall", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "defaultIncoming", "defaultOutgoing"] },
10462
+ "forgezero.openssh": { capability: "transport.ssh", versions: ["ubuntu-24.04", "ubuntu-26.04"], keys: ["installScope", "mode"] },
10435
10463
  "forgezero.systemd": { capability: "service.manager", versions: ["ubuntu-26.04"], keys: ["installScope", "credentials", "serviceSandbox"] }
10436
10464
  };
10437
10465
  const contract = expected[requirement2.provider];
@@ -11587,6 +11615,143 @@ import {
11587
11615
  import { dirname as dirname9 } from "path";
11588
11616
  import { fileURLToPath } from "url";
11589
11617
 
11618
+ // src/ubuntu.ts
11619
+ var current = OS_CATALOG[0];
11620
+ var SUPPORTED_GUEST_IMAGE = Object.freeze({
11621
+ key: current.imageKey,
11622
+ family: `${current.id}-${current.version}`,
11623
+ version: current.imageVersion,
11624
+ label: current.imageLabel,
11625
+ url: current.imageUrl,
11626
+ sha256: current.imageSha256
11627
+ });
11628
+ var SUPPORTED_GUEST_IMAGES = Object.freeze(OS_CATALOG.filter(({ status }) => status === "active").map(({ id: id2, imageKey, imageLabel, imageVersion, version, confidentialModes }) => ({
11629
+ key: imageKey,
11630
+ label: imageLabel,
11631
+ family: `${id2}-${version}`,
11632
+ version: imageVersion,
11633
+ confidential: confidentialModes.some((mode) => mode === "sev-snp")
11634
+ })));
11635
+ function assertSupportedGuestImage(imageKey, confidential = false) {
11636
+ const selected = OS_CATALOG.find((entry) => entry.status === "active" && entry.imageKey === imageKey);
11637
+ if (!selected) {
11638
+ throw new Error(`unsupported guest image ${imageKey}; supported images: ${SUPPORTED_GUEST_IMAGES.map(({ key }) => key).join(", ")}`);
11639
+ }
11640
+ if (confidential && !selected.confidentialModes.some((mode) => mode === "sev-snp")) {
11641
+ throw new Error(`guest image ${imageKey} is not approved for SEV-SNP`);
11642
+ }
11643
+ }
11644
+
11645
+ // src/platform-genesis.ts
11646
+ var SAFE_NAME = /^[a-z][a-z0-9-]{0,62}$/;
11647
+ var SAFE_POOL = /^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$/;
11648
+ var PRIVATE_V4 = /^(?:10\.(?:\d{1,3}\.){2}\d{1,3}|192\.168\.(?:\d{1,3})\.(?:\d{1,3})|172\.(?:1[6-9]|2\d|3[01])\.(?:\d{1,3})\.(?:\d{1,3}))$/;
11649
+ var integer2 = (value, minimum, maximum, label) => {
11650
+ if (!Number.isInteger(value) || value < minimum || value > maximum) {
11651
+ throw new Error(`platform genesis ${label} is outside the supported range`);
11652
+ }
11653
+ return value;
11654
+ };
11655
+ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
11656
+ if (!Array.isArray(guests) || guests.length !== expectedCount) {
11657
+ throw new Error(`platform genesis requires exactly ${expectedCount} reviewed guest requests`);
11658
+ }
11659
+ const names = new Set;
11660
+ const addresses = new Set;
11661
+ const pools = new Set;
11662
+ return guests.map((input) => {
11663
+ if (!input || typeof input !== "object" || !SAFE_NAME.test(input.name)) {
11664
+ throw new Error("platform genesis guest name is malformed");
11665
+ }
11666
+ if (!PRIVATE_V4.test(input.address))
11667
+ throw new Error("platform genesis guest address must be private IPv4");
11668
+ if (typeof input.confidential !== "boolean")
11669
+ throw new Error("platform genesis confidential requirement is missing");
11670
+ assertSupportedGuestImage(input.imageKey, input.confidential);
11671
+ if (input.cpuPoolKey !== undefined && !SAFE_POOL.test(input.cpuPoolKey)) {
11672
+ throw new Error("platform genesis CPU pool key is malformed");
11673
+ }
11674
+ if (names.has(input.name) || addresses.has(input.address) || input.cpuPoolKey !== undefined && pools.has(input.cpuPoolKey)) {
11675
+ throw new Error("platform genesis guest names, addresses and explicit CPU pools must be unique");
11676
+ }
11677
+ names.add(input.name);
11678
+ addresses.add(input.address);
11679
+ if (input.cpuPoolKey !== undefined)
11680
+ pools.add(input.cpuPoolKey);
11681
+ const physicalCores = integer2(input.physicalCores, 1, 256, "physical core count");
11682
+ const vcpu = integer2(input.vcpu, 1, 512, "vCPU count");
11683
+ if (vcpu < physicalCores)
11684
+ throw new Error("platform genesis vCPU count cannot be smaller than physical cores");
11685
+ const memoryGib = integer2(input.memoryGib, 1, 8192, "memory");
11686
+ const diskGib = integer2(input.diskGib, 8, 65536, "disk");
11687
+ const egressGuaranteedMbps = integer2(input.egressGuaranteedMbps, 0, 1e6, "guaranteed egress");
11688
+ const egressBurstMbps = integer2(input.egressBurstMbps, egressGuaranteedMbps, 1e6, "burst egress");
11689
+ return {
11690
+ name: input.name,
11691
+ address: input.address,
11692
+ imageKey: input.imageKey,
11693
+ ...input.cpuPoolKey ? { cpuPoolKey: input.cpuPoolKey } : {},
11694
+ physicalCores,
11695
+ vcpu,
11696
+ memoryGib,
11697
+ diskGib,
11698
+ egressGuaranteedMbps,
11699
+ egressBurstMbps,
11700
+ confidential: input.confidential
11701
+ };
11702
+ });
11703
+ }
11704
+ var publicKey = (value) => {
11705
+ const normalized = value.trim();
11706
+ if (!/^ssh-(?:ed25519|rsa) [A-Za-z0-9+/]+={0,3}(?: [^\r\n]+)?$/.test(normalized)) {
11707
+ throw new Error("platform genesis SSH public key is malformed");
11708
+ }
11709
+ return normalized;
11710
+ };
11711
+ var claimFor = (guest, keys, action) => {
11712
+ const common = {
11713
+ computeKey: `platform:${guest.name}`,
11714
+ claimToken: "offline-platform-genesis",
11715
+ claimExpiresAtTs: Number.MAX_SAFE_INTEGER,
11716
+ attempt: 1,
11717
+ bootstrap: { kind: "platform-genesis" },
11718
+ spec: {
11719
+ reference: `platform:${guest.name}`,
11720
+ imageKey: guest.imageKey,
11721
+ guestName: guest.name,
11722
+ guestAddress: guest.address,
11723
+ ...guest.cpuPoolKey ? { cpuPoolKey: guest.cpuPoolKey } : {},
11724
+ physicalCores: guest.physicalCores,
11725
+ vcpu: guest.vcpu,
11726
+ memoryGib: guest.memoryGib,
11727
+ diskGib: guest.diskGib,
11728
+ egressGuaranteedMbps: guest.egressGuaranteedMbps,
11729
+ egressBurstMbps: guest.egressBurstMbps,
11730
+ confidential: guest.confidential
11731
+ }
11732
+ };
11733
+ if (action === "delete")
11734
+ return { ...common, action: "delete" };
11735
+ return { ...common, action: "create", access: { sshUser: "forgezero", sshPublicKeys: [...keys] } };
11736
+ };
11737
+ function platformGenesisClaims(guests, sshPublicKeys) {
11738
+ const reviewed = validatePlatformGenesisGuests(guests);
11739
+ const keys = [...new Set(sshPublicKeys.map(publicKey))];
11740
+ if (keys.length === 0)
11741
+ throw new Error("platform genesis requires at least one operator SSH public key");
11742
+ return reviewed.map((guest) => claimFor(guest, keys, "create"));
11743
+ }
11744
+ function platformCommunityRehearsalClaims(seedGuests, rehearsalGuest, sshPublicKeys) {
11745
+ const reviewed = validatePlatformGenesisGuests([...seedGuests, rehearsalGuest], 4);
11746
+ const keys = [...new Set(sshPublicKeys.map(publicKey))];
11747
+ if (keys.length === 0)
11748
+ throw new Error("platform genesis requires at least one operator SSH public key");
11749
+ return reviewed.map((guest) => claimFor(guest, keys, "create"));
11750
+ }
11751
+ function platformCommunityRehearsalDeletionClaims(seedGuests, rehearsalGuest) {
11752
+ return validatePlatformGenesisGuests([...seedGuests, rehearsalGuest], 4).map((guest) => claimFor(guest, [], "delete"));
11753
+ }
11754
+
11590
11755
  // src/platform-bootstrap-runtime.ts
11591
11756
  var safeAtom = (name, value) => {
11592
11757
  if (!value || /[\0\r\n]/.test(value))
@@ -11618,6 +11783,50 @@ var httpsOrigin = (name, raw) => {
11618
11783
  }
11619
11784
  return value.origin;
11620
11785
  };
11786
+ function validatePlatformInitialInventory(value) {
11787
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !["metalHostname", "region", "computes"].includes(key)) || !/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(value.metalHostname) || !value.region || typeof value.region !== "object" || Array.isArray(value.region) || Object.keys(value.region).some((key) => !["key", "label", "country", "city", "confidentialCapable"].includes(key)) || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.region.key) || !/^[A-Z]{2}$/.test(value.region.country) || value.region.confidentialCapable !== true || !Array.isArray(value.computes)) {
11788
+ throw new Error("initial platform inventory coordinates are invalid");
11789
+ }
11790
+ safeAtom("initialInventory.region.label", value.region.label);
11791
+ if (value.region.city !== undefined)
11792
+ safeAtom("initialInventory.region.city", value.region.city);
11793
+ const computes = validatePlatformGenesisGuests(value.computes.map((compute) => {
11794
+ if (!compute || typeof compute !== "object" || Array.isArray(compute) || Object.keys(compute).some((key) => ![
11795
+ "name",
11796
+ "address",
11797
+ "imageKey",
11798
+ "cpuPoolKey",
11799
+ "physicalCores",
11800
+ "vcpu",
11801
+ "memoryGib",
11802
+ "diskGib",
11803
+ "egressGuaranteedMbps",
11804
+ "egressBurstMbps",
11805
+ "confidential",
11806
+ "databaseRole",
11807
+ "databaseAgency"
11808
+ ].includes(key)))
11809
+ throw new Error("initial platform compute contains unknown fields");
11810
+ return compute;
11811
+ }));
11812
+ for (let index = 0;index < computes.length; index += 1) {
11813
+ const declared = value.computes[index];
11814
+ const expectedRole = index === 0 ? "master" : "joiner";
11815
+ if (declared.databaseRole !== expectedRole || declared.databaseAgency !== "member" || declared.confidential !== true) {
11816
+ throw new Error("initial platform inventory requires one master, two joiners, three Agency members and confidential compute");
11817
+ }
11818
+ }
11819
+ return {
11820
+ metalHostname: value.metalHostname,
11821
+ region: { ...value.region },
11822
+ computes: value.computes.map((compute, index) => ({
11823
+ ...computes[index],
11824
+ databaseRole: compute.databaseRole,
11825
+ databaseAgency: compute.databaseAgency,
11826
+ confidential: true
11827
+ }))
11828
+ };
11829
+ }
11621
11830
  var systemdValue = (name, raw) => {
11622
11831
  if (/[\0\r\n]/.test(raw))
11623
11832
  throw new Error(`${name} must be single-line.`);
@@ -11703,12 +11912,14 @@ function validatePlatformSharedEnvironment(input) {
11703
11912
  throw new Error("realtime.producer is invalid.");
11704
11913
  }
11705
11914
  }
11915
+ const initialInventory = input.initialInventory ? validatePlatformInitialInventory(input.initialInventory) : undefined;
11706
11916
  return {
11707
11917
  ...input,
11708
11918
  databaseCoordinators: coordinators,
11709
11919
  appOrigin: httpsOrigin("appOrigin", input.appOrigin),
11710
11920
  apiOrigin: httpsOrigin("apiOrigin", input.apiOrigin),
11711
- agentOtlpEndpoint: httpsOrigin("agentOtlpEndpoint", input.agentOtlpEndpoint)
11921
+ agentOtlpEndpoint: httpsOrigin("agentOtlpEndpoint", input.agentOtlpEndpoint),
11922
+ ...initialInventory ? { initialInventory } : {}
11712
11923
  };
11713
11924
  }
11714
11925
  function renderPlatformSharedEnvironment(input) {
@@ -11771,7 +11982,8 @@ function renderPlatformSharedEnvironment(input) {
11771
11982
  FZ_CF_TUNNEL_SERVICE: value.cloudflare?.tunnelService ?? "",
11772
11983
  FZ_REALTIME_WORKER_SCRIPT: value.realtime?.workerScriptName ?? "",
11773
11984
  FZ_REALTIME_ENDPOINT: value.realtime?.endpoint ?? "",
11774
- FZ_REALTIME_PRODUCER: value.realtime?.producer ?? ""
11985
+ FZ_REALTIME_PRODUCER: value.realtime?.producer ?? "",
11986
+ FZ_PLATFORM_INITIAL_INVENTORY: value.initialInventory ? JSON.stringify(value.initialInventory) : ""
11775
11987
  };
11776
11988
  return `# Generated by fz bootstrap platform. Non-secret coordinates only.
11777
11989
  ` + Object.entries(entries).map(([key, entry]) => `${key}=${systemdValue(key, entry)}`).join(`
@@ -12063,12 +12275,12 @@ async function ensureCloudflarePrivateRoute(config, fetcher = fetch) {
12063
12275
  }
12064
12276
  const path = `/accounts/${config.accountId}/teamnet/routes`;
12065
12277
  const routes = await cf(config, path, {}, fetcher);
12066
- const current = routes.find((route2) => !route2.deleted_at && route2.network === config.network && (route2.virtual_network_id ?? "") === (config.virtualNetworkId ?? ""));
12067
- if (current) {
12068
- if (current.tunnel_id !== config.tunnelId) {
12278
+ const current2 = routes.find((route2) => !route2.deleted_at && route2.network === config.network && (route2.virtual_network_id ?? "") === (config.virtualNetworkId ?? ""));
12279
+ if (current2) {
12280
+ if (current2.tunnel_id !== config.tunnelId) {
12069
12281
  throw new Error(`private route ${config.network} already belongs to another Tunnel`);
12070
12282
  }
12071
- return { route: current, created: false };
12283
+ return { route: current2, created: false };
12072
12284
  }
12073
12285
  const route = await cf(config, path, {
12074
12286
  method: "POST",
@@ -12117,7 +12329,7 @@ async function ensureCloudflareWarpNetworkIncludes(config, fetcher = fetch) {
12117
12329
  async function configureCloudflareEdge(config, fetcher = fetch) {
12118
12330
  const tunnelPath = `/accounts/${config.accountId}/cfd_tunnel/${config.tunnelId}/configurations`;
12119
12331
  const dnsPath = `/zones/${config.zoneId}/dns_records`;
12120
- const [current, records] = await Promise.all([
12332
+ const [current2, records] = await Promise.all([
12121
12333
  cf(config, tunnelPath, {}, fetcher),
12122
12334
  cf(config, `${dnsPath}?name=${encodeURIComponent(config.hostname)}&per_page=1000`, {}, fetcher)
12123
12335
  ]);
@@ -12128,7 +12340,7 @@ async function configureCloudflareEdge(config, fetcher = fetch) {
12128
12340
  if (existingRecord && (existingRecord.type !== "CNAME" || existingRecord.name && existingRecord.name.toLowerCase() !== config.hostname.toLowerCase())) {
12129
12341
  throw new Error(`Cloudflare DNS hostname ${config.hostname} is already owned by an incompatible ${existingRecord.type ?? "unknown"} record`);
12130
12342
  }
12131
- const existing = current.config?.ingress ?? [];
12343
+ const existing = current2.config?.ingress ?? [];
12132
12344
  const catchAll = existing.filter((rule) => !("hostname" in rule));
12133
12345
  const otherHosts = existing.filter((rule) => ("hostname" in rule) && rule.hostname !== config.hostname);
12134
12346
  const desiredIngress = [
@@ -13215,7 +13427,7 @@ async function sha256File(path) {
13215
13427
  });
13216
13428
  return hash.digest("hex");
13217
13429
  }
13218
- var branchName = (value) => {
13430
+ var bootstrapBundleBranch = (value) => {
13219
13431
  if (typeof value !== "string" || !/^[A-Za-z0-9](?:[A-Za-z0-9._/-]{0,126}[A-Za-z0-9])?$/.test(value) || value.includes("..") || value.includes("//") || value.startsWith("-")) {
13220
13432
  throw new Error("bootstrap bundle branch is malformed");
13221
13433
  }
@@ -13232,7 +13444,7 @@ function parseBootstrapBundleManifest(value) {
13232
13444
  if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
13233
13445
  throw new Error("bootstrap bundle manifest format is unsupported");
13234
13446
  }
13235
- branchName(source.branch);
13447
+ bootstrapBundleBranch(source.branch);
13236
13448
  if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
13237
13449
  throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
13238
13450
  }
@@ -13277,7 +13489,7 @@ async function buildBootstrapBundle(input, exec = run) {
13277
13489
  const repositoryRoot = resolve6(input.repositoryRoot);
13278
13490
  const outputPath = resolve6(input.outputPath);
13279
13491
  const manifestPath = `${outputPath}.json`;
13280
- const branch = branchName(input.branch);
13492
+ const branch = bootstrapBundleBranch(input.branch);
13281
13493
  if (!isAbsolute2(input.outputPath) || outputPath !== input.outputPath) {
13282
13494
  throw new Error("bootstrap bundle output must be a canonical absolute path");
13283
13495
  }
@@ -13502,6 +13714,12 @@ function validateBootstrapConfig(value) {
13502
13714
  throw new Error("Cloudflare Mesh/WARP requires a node-specific Cloudflare handoff");
13503
13715
  }
13504
13716
  if (value.kind === "enrolled-compute") {
13717
+ const os = OS_CATALOG.find((entry) => entry.status === "active" && entry.id === value.operatingSystem?.id && entry.version === value.operatingSystem?.version && entry.architecture === value.operatingSystem?.architecture);
13718
+ if (!os)
13719
+ throw new Error("enrolled compute operating system is not in the active typed OS catalog");
13720
+ if (value.operatingSystem.version === "24.04" && (value.installCloudflared || value.installWarp)) {
13721
+ throw new Error("Cloudflare Tunnel and WARP require the reviewed Ubuntu 26.04 strategy");
13722
+ }
13505
13723
  if (value.gitDeployKey !== undefined && typeof value.gitDeployKey !== "boolean") {
13506
13724
  throw new Error("gitDeployKey must be boolean");
13507
13725
  }
@@ -13533,6 +13751,7 @@ function validateBootstrapConfig(value) {
13533
13751
  if (!value.bootstrapBundle || ![value.bootstrapBundle.bundleFile, value.bootstrapBundle.manifestFile].every((path) => typeof path === "string" && path.startsWith("/") && !/[\r\n\0:]/.test(path))) {
13534
13752
  throw new Error("platform bootstrap requires absolute bundle and manifest paths");
13535
13753
  }
13754
+ bootstrapBundleBranch(value.bootstrapBundle.branch);
13536
13755
  let api;
13537
13756
  try {
13538
13757
  api = new URL(value.apiUrl);
@@ -13587,9 +13806,19 @@ function validateBootstrapConfig(value) {
13587
13806
  }
13588
13807
  function planBootstrap(input, initialized = false) {
13589
13808
  const config = validateBootstrapConfig(structuredClone(input));
13809
+ const enrolledOsVersion = config.kind === "enrolled-compute" ? `ubuntu-${config.operatingSystem.version}` : undefined;
13810
+ const enrolledSoftware = config.kind === "enrolled-compute" ? [...config.software ?? []] : [];
13811
+ if (config.kind === "enrolled-compute") {
13812
+ if (!enrolledSoftware.some(({ id: id2 }) => id2 === "bun"))
13813
+ enrolledSoftware.push({ id: "bun", version: "1.3.14" });
13814
+ if (!enrolledSoftware.some(({ id: id2 }) => id2 === "git"))
13815
+ enrolledSoftware.push({ id: "git", version: enrolledOsVersion });
13816
+ if (!enrolledSoftware.some(({ id: id2 }) => id2 === "ufw"))
13817
+ enrolledSoftware.push({ id: "ufw", version: enrolledOsVersion });
13818
+ }
13590
13819
  const software = config.kind === "enrolled-compute" ? [
13591
- ...config.software ?? [],
13592
- ...config.bootstrapRunner && !config.software?.some(({ id: id2 }) => id2 === "openssh-client") ? [{ id: "openssh-client", version: "ubuntu-26.04" }] : [],
13820
+ ...enrolledSoftware,
13821
+ ...config.bootstrapRunner && !enrolledSoftware.some(({ id: id2 }) => id2 === "openssh-client") ? [{ id: "openssh-client", version: enrolledOsVersion }] : [],
13593
13822
  ...config.installCloudflared ? [{ id: "cloudflared", version: "2026.7.3" }] : [],
13594
13823
  ...config.installWarp ? [{ id: "cloudflare-warp", version: "2026.6.822.0-min" }] : []
13595
13824
  ] : [
@@ -13824,9 +14053,8 @@ async function verifyBootstrapBundleOnHost(host, config, verifyGit = false) {
13824
14053
  throw new Error("bootstrap bundle manifest is not valid JSON");
13825
14054
  }
13826
14055
  const manifest = parseBootstrapBundleManifest(parsed);
13827
- const expectedBranch = config.environment === "production" ? "main" : "dev";
13828
- if (manifest.branch !== expectedBranch || bundleMetadata && bundleMetadata.size !== manifest.bytes) {
13829
- throw new Error("bootstrap bundle manifest disagrees with the selected platform environment");
14056
+ if (manifest.branch !== config.bootstrapBundle.branch || bundleMetadata && bundleMetadata.size !== manifest.bytes) {
14057
+ throw new Error("bootstrap bundle manifest disagrees with the reviewed bootstrap bundle coordinates");
13830
14058
  }
13831
14059
  const digest = (await checked3(host, ["/usr/bin/sha256sum", config.bootstrapBundle.bundleFile], "bootstrap bundle digest")).trim().split(/\s+/)[0];
13832
14060
  if (digest !== manifest.sha256)
@@ -13840,6 +14068,7 @@ function bootstrapIdentity(config) {
13840
14068
  if (config.kind === "enrolled-compute")
13841
14069
  return {
13842
14070
  kind: config.kind,
14071
+ operatingSystem: config.operatingSystem,
13843
14072
  apiUrl: config.apiUrl,
13844
14073
  realm: config.realm,
13845
14074
  nodeHostname: config.nodeHostname,
@@ -14453,7 +14682,7 @@ function strictBootstrapDocument(value) {
14453
14682
  "bootstrapRunner"
14454
14683
  ], "bootstrap config");
14455
14684
  if (root.kind === "platform") {
14456
- exactKeys2(root.bootstrapBundle, ["bundleFile", "manifestFile"], "bootstrap bundle config");
14685
+ exactKeys2(root.bootstrapBundle, ["bundleFile", "manifestFile", "branch"], "bootstrap bundle config");
14457
14686
  exactKeys2(root.firewall, ["enabled", "sshPort", "privateCidrs"], "firewall config");
14458
14687
  if (root.cloudflareHandoff !== undefined)
14459
14688
  exactKeys2(root.cloudflareHandoff, ["handoffFile", "nodeName"], "Cloudflare handoff");
@@ -14500,9 +14729,32 @@ function strictBootstrapDocument(value) {
14500
14729
  "otlpTraceSampleRatio",
14501
14730
  "backup",
14502
14731
  "cloudflare",
14503
- "realtime"
14732
+ "realtime",
14733
+ "initialInventory"
14504
14734
  ], "runtime environment");
14505
14735
  const environment = runtime.environment;
14736
+ if (environment.initialInventory !== undefined) {
14737
+ const inventory = exactKeys2(environment.initialInventory, ["metalHostname", "region", "computes"], "initial inventory");
14738
+ exactKeys2(inventory.region, ["key", "label", "country", "city", "confidentialCapable"], "initial inventory region");
14739
+ if (!Array.isArray(inventory.computes))
14740
+ throw new Error("initial inventory computes must be an array");
14741
+ for (const compute of inventory.computes)
14742
+ exactKeys2(compute, [
14743
+ "name",
14744
+ "address",
14745
+ "imageKey",
14746
+ "cpuPoolKey",
14747
+ "physicalCores",
14748
+ "vcpu",
14749
+ "memoryGib",
14750
+ "diskGib",
14751
+ "egressGuaranteedMbps",
14752
+ "egressBurstMbps",
14753
+ "confidential",
14754
+ "databaseRole",
14755
+ "databaseAgency"
14756
+ ], "initial inventory compute");
14757
+ }
14506
14758
  if (environment.email !== undefined) {
14507
14759
  const email = exactKeys2(environment.email, ["provider", "host", "port", "user", "from", "eu"], "email config");
14508
14760
  if (email.provider === "smtp")
@@ -14519,6 +14771,7 @@ function strictBootstrapDocument(value) {
14519
14771
  if (environment.realtime !== undefined)
14520
14772
  exactKeys2(environment.realtime, ["workerScriptName", "endpoint", "producer"], "realtime runtime config");
14521
14773
  } else if (root.kind === "enrolled-compute") {
14774
+ exactKeys2(root.operatingSystem, ["id", "version", "architecture"], "enrolled compute operating system");
14522
14775
  if (root.cloudflareHandoff !== undefined)
14523
14776
  exactKeys2(root.cloudflareHandoff, ["handoffFile", "nodeName"], "Cloudflare handoff");
14524
14777
  if (root.bootstrapRunner !== undefined)
@@ -14850,19 +15103,7 @@ import { join as join8 } from "path";
14850
15103
  // src/metal-provision.ts
14851
15104
  import { dirname as dirname11, isAbsolute as isAbsolute3, join as join7 } from "path";
14852
15105
  import { isIP as isIP4 } from "net";
14853
-
14854
- // src/ubuntu.ts
14855
- var SUPPORTED_GUEST_IMAGE = Object.freeze({
14856
- key: "ubuntu-resolute-20260731",
14857
- family: "ubuntu-26.04",
14858
- version: "2026-07-31",
14859
- label: "Ubuntu 26.04 LTS Resolute",
14860
- url: "https://cloud-images.ubuntu.com/releases/resolute/release-20260731/ubuntu-26.04-server-cloudimg-amd64.img",
14861
- sha256: "9dc7c5363c0146a08ba0c9aa834d82c2c6dfbb1c471ad9a2f0aba1189e21be05"
14862
- });
14863
-
14864
- // src/metal-provision.ts
14865
- var SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/;
15106
+ var SAFE_NAME2 = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/;
14866
15107
  var DEVICE = /^[a-zA-Z][a-zA-Z0-9_.-]{0,14}$/;
14867
15108
  var IPV4_PREFIX = /^(?:25[0-5]|2[0-4]\d|1?\d?\d)\.(?:25[0-5]|2[0-4]\d|1?\d?\d)\.(?:25[0-5]|2[0-4]\d|1?\d?\d)$/;
14868
15109
  var LINUX_LIST = /^\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*$/;
@@ -14888,7 +15129,7 @@ function membersOfLinuxList(value, label) {
14888
15129
  return members;
14889
15130
  }
14890
15131
  function validateMetalProfile(profile) {
14891
- if (!SAFE_NAME.test(profile.volumeGroup))
15132
+ if (!SAFE_NAME2.test(profile.volumeGroup))
14892
15133
  throw new MetalProvisionError("invalid volume group");
14893
15134
  if (!DEVICE.test(profile.bridge))
14894
15135
  throw new MetalProvisionError("invalid bridge");
@@ -14910,9 +15151,16 @@ function validateMetalProfile(profile) {
14910
15151
  if (telemetryEndpoint.protocol !== "https:" || telemetryEndpoint.username || telemetryEndpoint.password || telemetryEndpoint.search || telemetryEndpoint.hash || isIP4(telemetryEndpoint.hostname) !== 0 || !telemetryEndpoint.hostname.includes(".") || telemetryEndpoint.hostname === "localhost" || telemetryEndpoint.hostname.endsWith(".local"))
14911
15152
  throw new MetalProvisionError("Agent telemetry endpoint must be a public HTTPS DNS coordinate without credentials, query or fragment");
14912
15153
  const imageKeys = Object.keys(profile.images);
14913
- if (imageKeys.length !== 1 || imageKeys[0] !== SUPPORTED_GUEST_IMAGE.key || profile.images[SUPPORTED_GUEST_IMAGE.key]?.sha256 !== SUPPORTED_GUEST_IMAGE.sha256) {
14914
- throw new MetalProvisionError(`metal profile must contain only the pinned ${SUPPORTED_GUEST_IMAGE.key} image contract`);
15154
+ if (imageKeys.length === 0)
15155
+ throw new MetalProvisionError("metal profile requires at least one pinned guest image");
15156
+ for (const imageKey of imageKeys) {
15157
+ const catalog = OS_CATALOG.find((entry) => entry.status === "active" && entry.imageKey === imageKey);
15158
+ if (!catalog || profile.images[imageKey]?.sha256 !== catalog.imageSha256) {
15159
+ throw new MetalProvisionError(`metal profile image ${imageKey} does not match the pinned OS catalog`);
15160
+ }
14915
15161
  }
15162
+ if (profile.confidential && !imageKeys.some((key) => OS_CATALOG.find((entry) => entry.imageKey === key)?.confidentialModes.some((mode) => mode === "sev-snp")))
15163
+ throw new MetalProvisionError("confidential metal profile requires a pinned SEV-SNP guest image");
14916
15164
  if (!Array.isArray(profile.cpuPools) || profile.cpuPools.length === 0) {
14917
15165
  throw new MetalProvisionError("at least one exclusive CPU pool is required");
14918
15166
  }
@@ -14921,7 +15169,7 @@ function validateMetalProfile(profile) {
14921
15169
  const assignedMemory = new Set;
14922
15170
  let poolsWithMemory = 0;
14923
15171
  for (const pool of profile.cpuPools) {
14924
- if (!SAFE_NAME.test(pool.key) || keys.has(pool.key))
15172
+ if (!SAFE_NAME2.test(pool.key) || keys.has(pool.key))
14925
15173
  throw new MetalProvisionError("invalid or duplicate CPU pool key");
14926
15174
  keys.add(pool.key);
14927
15175
  const cpus = membersOfLinuxList(pool.cpus, "CPU");
@@ -15163,10 +15411,9 @@ function validateMetalBootstrapConfig(config) {
15163
15411
  if (config.profile.stateDir !== "/etc/forgezero/metal-guests" || config.profile.seedDir !== "/var/lib/forgezero/seed" || config.profile.unitDir !== UNIT_DIRECTORY || config.profile.legacyStateDir !== undefined) {
15164
15412
  throw new MetalBootstrapError("metal bootstrap uses fixed state, seed, and systemd unit directories");
15165
15413
  }
15166
- const image = config.profile.images[Object.keys(config.profile.images)[0]];
15167
- if (!image.path.startsWith("/var/lib/forgezero/images/") || resolve8(image.path) !== image.path || /[\0\r\n]/.test(image.path)) {
15168
- throw new MetalBootstrapError("the pinned guest image must use the fixed image directory");
15169
- }
15414
+ for (const image of Object.values(config.profile.images))
15415
+ if (!image.path.startsWith("/var/lib/forgezero/images/") || resolve8(image.path) !== image.path || /[\0\r\n]/.test(image.path))
15416
+ throw new MetalBootstrapError("every pinned guest image must use the fixed image directory");
15170
15417
  if (config.profile.bunVersion !== SUPPORTED_BUN_VERSION || config.profile.bunReleaseSha256 !== SUPPORTED_BUN_RELEASE_SHA256 || config.profile.agentVersion !== VERSION2) {
15171
15418
  throw new MetalBootstrapError("metal guest runtime must use the package-owned Bun and Agent release coordinates");
15172
15419
  }
@@ -15468,9 +15715,11 @@ var preflight = async (config, exec) => {
15468
15715
  throw new MetalBootstrapError("/dev/sev is required by the confidential profile");
15469
15716
  await runChecked(exec, ["/usr/bin/qemu-system-x86_64", "-object", "sev-snp-guest,help"]);
15470
15717
  }
15471
- const digest = (await runChecked(exec, ["/usr/bin/sha256sum", config.profile.images[Object.keys(config.profile.images)[0]].path])).stdout.split(/\s+/)[0];
15472
- if (digest !== config.profile.images[Object.keys(config.profile.images)[0]].sha256)
15473
- throw new MetalBootstrapError("pinned guest image digest mismatch");
15718
+ for (const image of Object.values(config.profile.images)) {
15719
+ const digest = (await runChecked(exec, ["/usr/bin/sha256sum", image.path])).stdout.split(/\s+/)[0];
15720
+ if (digest !== image.sha256)
15721
+ throw new MetalBootstrapError("pinned guest image digest mismatch");
15722
+ }
15474
15723
  };
15475
15724
  var assertSupportedMetalHost = () => {
15476
15725
  if (process.platform !== "linux" || process.arch !== "x64") {
@@ -15482,34 +15731,38 @@ var assertSupportedMetalHost = () => {
15482
15731
  }
15483
15732
  };
15484
15733
  var ensurePinnedGuestImage = async (config, exec) => {
15485
- const image = config.profile.images[SUPPORTED_GUEST_IMAGE.key];
15486
- if (existsSync10(image.path))
15487
- return;
15488
- mkdirSync11(dirname12(image.path), { recursive: true, mode: 493 });
15489
- const temporary = `${image.path}.next-${process.pid}`;
15490
- try {
15491
- await runChecked(exec, [
15492
- "/usr/bin/curl",
15493
- "--fail",
15494
- "--location",
15495
- "--proto",
15496
- "=https",
15497
- "--tlsv1.2",
15498
- "--output",
15499
- temporary,
15500
- SUPPORTED_GUEST_IMAGE.url
15501
- ]);
15502
- const digest = (await runChecked(exec, ["/usr/bin/sha256sum", temporary])).stdout.split(/\s+/)[0];
15503
- if (digest !== image.sha256)
15504
- throw new MetalBootstrapError("downloaded guest image digest mismatch");
15505
- chmodSync5(temporary, 292);
15506
- chownSync(temporary, 0, 0);
15507
- renameSync9(temporary, image.path);
15508
- } catch (cause) {
15734
+ for (const [imageKey, image] of Object.entries(config.profile.images)) {
15735
+ if (existsSync10(image.path))
15736
+ continue;
15737
+ const catalog = OS_CATALOG.find((entry) => entry.status === "active" && entry.imageKey === imageKey);
15738
+ if (!catalog)
15739
+ throw new MetalBootstrapError(`guest image ${imageKey} is not active`);
15740
+ mkdirSync11(dirname12(image.path), { recursive: true, mode: 493 });
15741
+ const temporary = `${image.path}.next-${process.pid}`;
15509
15742
  try {
15510
- unlinkSync2(temporary);
15511
- } catch {}
15512
- throw cause;
15743
+ await runChecked(exec, [
15744
+ "/usr/bin/curl",
15745
+ "--fail",
15746
+ "--location",
15747
+ "--proto",
15748
+ "=https",
15749
+ "--tlsv1.2",
15750
+ "--output",
15751
+ temporary,
15752
+ catalog.imageUrl
15753
+ ]);
15754
+ const digest = (await runChecked(exec, ["/usr/bin/sha256sum", temporary])).stdout.split(/\s+/)[0];
15755
+ if (digest !== image.sha256)
15756
+ throw new MetalBootstrapError("downloaded guest image digest mismatch");
15757
+ chmodSync5(temporary, 292);
15758
+ chownSync(temporary, 0, 0);
15759
+ renameSync9(temporary, image.path);
15760
+ } catch (cause) {
15761
+ try {
15762
+ unlinkSync2(temporary);
15763
+ } catch {}
15764
+ throw cause;
15765
+ }
15513
15766
  }
15514
15767
  };
15515
15768
  async function applyMetalBootstrap(config, options) {
@@ -15669,10 +15922,17 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15669
15922
  ])).exitCode !== 0) {
15670
15923
  problems.push("local OTLP metrics receiver did not accept a proof request");
15671
15924
  }
15672
- const image = profile.images[Object.keys(profile.images)[0]];
15673
- if (existsSync10(image.path)) {
15925
+ imageVerified = true;
15926
+ for (const image of Object.values(profile.images)) {
15927
+ if (!existsSync10(image.path)) {
15928
+ imageVerified = false;
15929
+ break;
15930
+ }
15674
15931
  const digest = (await exec(["/usr/bin/sha256sum", image.path])).stdout.split(/\s+/)[0];
15675
- imageVerified = digest === image.sha256;
15932
+ if (digest !== image.sha256) {
15933
+ imageVerified = false;
15934
+ break;
15935
+ }
15676
15936
  }
15677
15937
  if (!imageVerified)
15678
15938
  problems.push("pinned guest image is absent or has the wrong digest");
@@ -15732,126 +15992,6 @@ import { isIP as isIP6 } from "net";
15732
15992
  import { tmpdir } from "os";
15733
15993
  import { basename as basename2, dirname as dirname13, isAbsolute as isAbsolute5, join as join10, resolve as resolve9 } from "path";
15734
15994
  import { fileURLToPath as fileURLToPath2 } from "url";
15735
-
15736
- // src/platform-genesis.ts
15737
- var PLATFORM_GENESIS_ENVIRONMENTS = ["production", "development"];
15738
- var productionShape = {
15739
- physicalCores: 8,
15740
- vcpu: 16,
15741
- memoryGib: 24,
15742
- diskGib: 200,
15743
- egressGuaranteedMbps: 1000,
15744
- egressBurstMbps: 2000
15745
- };
15746
- var developmentShape = {
15747
- physicalCores: 2,
15748
- vcpu: 4,
15749
- memoryGib: 8,
15750
- diskGib: 64,
15751
- egressGuaranteedMbps: 100,
15752
- egressBurstMbps: 250
15753
- };
15754
- var PLATFORM_GENESIS_FLEETS = {
15755
- production: [1, 2, 3].map((node) => ({
15756
- name: `fz-n${node}`,
15757
- address: `10.42.0.${10 + node}`,
15758
- cpuPoolKey: `node${node}`,
15759
- ...productionShape
15760
- })),
15761
- development: [1, 2, 3].map((node) => ({
15762
- name: `dev-fz-n${node}`,
15763
- address: `10.42.0.${20 + node}`,
15764
- cpuPoolKey: `node${node + 3}`,
15765
- ...developmentShape
15766
- }))
15767
- };
15768
- var DEVELOPMENT_COMMUNITY_REHEARSAL_GUEST = {
15769
- name: "dev-fz-n4",
15770
- address: "10.42.0.24",
15771
- cpuPoolKey: "node7",
15772
- ...developmentShape
15773
- };
15774
- var publicKey = (value) => {
15775
- const normalized = value.trim();
15776
- if (!/^ssh-(?:ed25519|rsa) [A-Za-z0-9+/]+={0,3}(?: [^\r\n]+)?$/.test(normalized)) {
15777
- throw new Error("platform genesis SSH public key is malformed");
15778
- }
15779
- return normalized;
15780
- };
15781
- function platformGenesisClaims(environment, sshPublicKeys) {
15782
- if (!PLATFORM_GENESIS_ENVIRONMENTS.includes(environment)) {
15783
- throw new Error("platform genesis environment must be production or development");
15784
- }
15785
- const keys = [...new Set(sshPublicKeys.map(publicKey))];
15786
- if (keys.length === 0)
15787
- throw new Error("platform genesis requires at least one operator SSH public key");
15788
- return PLATFORM_GENESIS_FLEETS[environment].map((guest) => ({
15789
- computeKey: `platform:${guest.name}`,
15790
- claimToken: "offline-platform-genesis",
15791
- claimExpiresAtTs: Number.MAX_SAFE_INTEGER,
15792
- attempt: 1,
15793
- action: "create",
15794
- bootstrap: { kind: "platform-genesis" },
15795
- access: { sshUser: "forgezero", sshPublicKeys: keys },
15796
- spec: {
15797
- reference: `platform:${guest.name}`,
15798
- imageKey: SUPPORTED_GUEST_IMAGE.key,
15799
- guestName: guest.name,
15800
- guestAddress: guest.address,
15801
- cpuPoolKey: guest.cpuPoolKey,
15802
- physicalCores: guest.physicalCores,
15803
- vcpu: guest.vcpu,
15804
- memoryGib: guest.memoryGib,
15805
- diskGib: guest.diskGib,
15806
- egressGuaranteedMbps: guest.egressGuaranteedMbps,
15807
- egressBurstMbps: guest.egressBurstMbps,
15808
- confidential: true
15809
- }
15810
- }));
15811
- }
15812
- function platformCommunityRehearsalClaims(sshPublicKeys) {
15813
- const keys = [...new Set(sshPublicKeys.map(publicKey))];
15814
- if (keys.length === 0)
15815
- throw new Error("platform genesis requires at least one operator SSH public key");
15816
- const seed = platformGenesisClaims("development", keys);
15817
- const guest = DEVELOPMENT_COMMUNITY_REHEARSAL_GUEST;
15818
- return [...seed, {
15819
- computeKey: `platform:${guest.name}`,
15820
- claimToken: "offline-platform-genesis",
15821
- claimExpiresAtTs: Number.MAX_SAFE_INTEGER,
15822
- attempt: 1,
15823
- action: "create",
15824
- bootstrap: { kind: "platform-genesis" },
15825
- access: { sshUser: "forgezero", sshPublicKeys: keys },
15826
- spec: {
15827
- reference: `platform:${guest.name}`,
15828
- imageKey: SUPPORTED_GUEST_IMAGE.key,
15829
- guestName: guest.name,
15830
- guestAddress: guest.address,
15831
- cpuPoolKey: guest.cpuPoolKey,
15832
- physicalCores: guest.physicalCores,
15833
- vcpu: guest.vcpu,
15834
- memoryGib: guest.memoryGib,
15835
- diskGib: guest.diskGib,
15836
- egressGuaranteedMbps: guest.egressGuaranteedMbps,
15837
- egressBurstMbps: guest.egressBurstMbps,
15838
- confidential: true
15839
- }
15840
- }];
15841
- }
15842
- function platformCommunityRehearsalDeletionClaims(sshPublicKeys) {
15843
- return platformCommunityRehearsalClaims(sshPublicKeys).map((claim) => ({
15844
- computeKey: claim.computeKey,
15845
- claimToken: claim.claimToken,
15846
- claimExpiresAtTs: claim.claimExpiresAtTs,
15847
- attempt: claim.attempt,
15848
- action: "delete",
15849
- bootstrap: { kind: "platform-genesis" },
15850
- spec: claim.spec
15851
- }));
15852
- }
15853
-
15854
- // src/operator-bootstrap.ts
15855
15995
  var REQUEST_LIMIT = 64 * 1024;
15856
15996
  var SECRET_LIMIT = 256 * 1024;
15857
15997
  var REMOTE_STAGE = "/run/forgezero-operator-bootstrap";
@@ -16031,10 +16171,44 @@ var validateMetalRequestValue = (value) => {
16031
16171
  throw new Error("metalConfigFile must be an absolute path");
16032
16172
  canonicalOutputPath(root.metalConfigFile, "metalConfigFile");
16033
16173
  readMetalBootstrapConfig(root.metalConfigFile);
16034
- const genesis = exactKeys5(root.genesis, ["environment", "sshPublicKeyFiles"], "operator metal genesis");
16035
- if (typeof genesis.environment !== "string" || !PLATFORM_GENESIS_ENVIRONMENTS.includes(genesis.environment)) {
16036
- throw new Error("operator metal genesis environment must be production or development");
16037
- }
16174
+ const genesis = exactKeys5(root.genesis, ["nodes", "rehearsalNode", "sshPublicKeyFiles"], "operator metal genesis");
16175
+ const guest = (value2, label) => {
16176
+ const row2 = exactKeys5(value2, [
16177
+ "name",
16178
+ "address",
16179
+ "imageKey",
16180
+ "cpuPoolKey",
16181
+ "physicalCores",
16182
+ "vcpu",
16183
+ "memoryGib",
16184
+ "diskGib",
16185
+ "egressGuaranteedMbps",
16186
+ "egressBurstMbps",
16187
+ "confidential"
16188
+ ], label);
16189
+ if (typeof row2.name !== "string" || typeof row2.address !== "string" || typeof row2.imageKey !== "string" || row2.cpuPoolKey !== undefined && typeof row2.cpuPoolKey !== "string" || !["physicalCores", "vcpu", "memoryGib", "diskGib", "egressGuaranteedMbps", "egressBurstMbps"].every((key) => typeof row2[key] === "number") || typeof row2.confidential !== "boolean") {
16190
+ throw new Error(`${label} has invalid resource coordinates`);
16191
+ }
16192
+ return {
16193
+ name: row2.name,
16194
+ address: row2.address,
16195
+ imageKey: row2.imageKey,
16196
+ ...row2.cpuPoolKey ? { cpuPoolKey: row2.cpuPoolKey } : {},
16197
+ physicalCores: row2.physicalCores,
16198
+ vcpu: row2.vcpu,
16199
+ memoryGib: row2.memoryGib,
16200
+ diskGib: row2.diskGib,
16201
+ egressGuaranteedMbps: row2.egressGuaranteedMbps,
16202
+ egressBurstMbps: row2.egressBurstMbps,
16203
+ confidential: row2.confidential
16204
+ };
16205
+ };
16206
+ if (!Array.isArray(genesis.nodes))
16207
+ throw new Error("operator metal genesis nodes must be an array");
16208
+ const nodes = validatePlatformGenesisGuests(genesis.nodes.map((row2, index) => guest(row2, `operator metal genesis node ${index + 1}`)));
16209
+ const rehearsalNode = genesis.rehearsalNode === undefined ? undefined : guest(genesis.rehearsalNode, "operator metal rehearsal node");
16210
+ if (rehearsalNode)
16211
+ validatePlatformGenesisGuests([...nodes, rehearsalNode], 4);
16038
16212
  if (!Array.isArray(genesis.sshPublicKeyFiles) || genesis.sshPublicKeyFiles.length === 0 || genesis.sshPublicKeyFiles.some((entry) => typeof entry !== "string")) {
16039
16213
  throw new Error("operator metal genesis requires SSH public-key files");
16040
16214
  }
@@ -16049,7 +16223,8 @@ var validateMetalRequestValue = (value) => {
16049
16223
  agentSocket: targetValue.agentSocket
16050
16224
  },
16051
16225
  genesis: {
16052
- environment: genesis.environment,
16226
+ nodes,
16227
+ ...rehearsalNode ? { rehearsalNode } : {},
16053
16228
  sshPublicKeyFiles: genesis.sshPublicKeyFiles
16054
16229
  }
16055
16230
  };
@@ -16064,17 +16239,17 @@ function writeOperatorMetalBootstrapRequest(path, request) {
16064
16239
  }
16065
16240
  function planOperatorMetalBootstrap(request, mode) {
16066
16241
  const config = readMetalBootstrapConfig(request.metalConfigFile);
16067
- if ((mode === "rehearsal" || mode === "rehearsal-cleanup") && request.genesis.environment !== "development") {
16068
- throw new Error("the disposable four-node Community rehearsal is development-only");
16069
- }
16070
16242
  const keys = request.genesis.sshPublicKeyFiles.map((path) => publicIdentity(path));
16071
- const claims = mode === "rehearsal" || mode === "rehearsal-cleanup" ? mode === "rehearsal-cleanup" ? platformCommunityRehearsalDeletionClaims(keys) : platformCommunityRehearsalClaims(keys) : platformGenesisClaims(request.genesis.environment, keys);
16243
+ if ((mode === "rehearsal" || mode === "rehearsal-cleanup") && !request.genesis.rehearsalNode) {
16244
+ throw new Error("the reviewed metal request has no rehearsal node");
16245
+ }
16246
+ const claims = mode === "rehearsal" || mode === "rehearsal-cleanup" ? mode === "rehearsal-cleanup" ? platformCommunityRehearsalDeletionClaims(request.genesis.nodes, request.genesis.rehearsalNode) : platformCommunityRehearsalClaims(request.genesis.nodes, request.genesis.rehearsalNode, keys) : platformGenesisClaims(request.genesis.nodes, keys);
16072
16247
  return {
16073
16248
  kind: "metal-remote",
16074
16249
  mode,
16075
16250
  mutation: mode !== "status",
16076
16251
  target: { address: request.target.address, port: request.target.port, user: request.target.user },
16077
- steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : mode === "genesis" || mode === "rehearsal" || mode === "rehearsal-cleanup" ? ["verify pinned SSH transport", mode === "rehearsal" ? "send the fixed three-node seed plus ordinary n4 Community rehearsal claim to the constrained Metal helper" : mode === "rehearsal-cleanup" ? "delete the exact disposable dev-fz-n1..n4 guests and their owned disks, units, seed data and inventory" : "send three fixed platform-genesis claims to the constrained Metal helper"] : [
16252
+ steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : mode === "genesis" || mode === "rehearsal" || mode === "rehearsal-cleanup" ? ["verify pinned SSH transport", mode === "rehearsal" ? "send the three reviewed seed claims plus the reviewed fourth Community rehearsal claim to the constrained Metal helper" : mode === "rehearsal-cleanup" ? "delete the exact four reviewed disposable guests and their owned disks, units, seed data and inventory" : "send the three reviewed platform-genesis claims to the constrained Metal helper"] : [
16078
16253
  "verify pinned SSH transport and caller-approved SSH agent",
16079
16254
  "copy pinned Bun and packaged fz artifacts to private staging",
16080
16255
  "stage the strict owner-only metal config and optional seed handoff",
@@ -16146,9 +16321,7 @@ function validatedPlatformFleet(fleet) {
16146
16321
  return config;
16147
16322
  });
16148
16323
  const environment = configs[0].environment;
16149
- const expected = PLATFORM_GENESIS_FLEETS[environment];
16150
- if (!expected || expected.length !== 3)
16151
- throw new Error("operator fleet environment is invalid");
16324
+ const expected = configs.map((config) => ({ name: config.computeReference, address: config.database.address }));
16152
16325
  const identity = fleetConfigIdentity(configs[0]);
16153
16326
  const operatorIdentity = `${requests[0].target.identityPublicKeyFile}\x00${requests[0].target.agentSocket}`;
16154
16327
  const jumpIdentity = JSON.stringify(requests[0].target.jump ?? null);
@@ -16319,13 +16492,17 @@ async function stageConfig(config, directory) {
16319
16492
  rewritten.cloudflareHandoff.handoffFile = remotePath;
16320
16493
  }
16321
16494
  const bundle = await readBootstrapBundle(config.bootstrapBundle.bundleFile, config.bootstrapBundle.manifestFile);
16495
+ if (bundle.manifest.branch !== config.bootstrapBundle.branch) {
16496
+ throw new Error("reviewed bootstrap config branch does not match the bundle manifest");
16497
+ }
16322
16498
  const bundleFiles = [
16323
16499
  { source: bundle.bundlePath, name: "bootstrap-api.bundle" },
16324
16500
  { source: bundle.manifestPath, name: "bootstrap-api.bundle.json" }
16325
16501
  ];
16326
16502
  rewritten.bootstrapBundle = {
16327
16503
  bundleFile: `${REMOTE_STAGE}/bootstrap-api.bundle`,
16328
- manifestFile: `${REMOTE_STAGE}/bootstrap-api.bundle.json`
16504
+ manifestFile: `${REMOTE_STAGE}/bootstrap-api.bundle.json`,
16505
+ branch: bundle.manifest.branch
16329
16506
  };
16330
16507
  const path = join10(directory, "platform-config.json");
16331
16508
  writeFileSync12(path, `${JSON.stringify(rewritten, null, 2)}
@@ -16483,13 +16660,16 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
16483
16660
  }
16484
16661
  if (mode === "genesis" || mode === "rehearsal" || mode === "rehearsal-cleanup") {
16485
16662
  const keys = request.genesis.sshPublicKeyFiles.map((path) => publicIdentity(path));
16663
+ if ((mode === "rehearsal" || mode === "rehearsal-cleanup") && !request.genesis.rehearsalNode) {
16664
+ throw new Error("the reviewed metal request has no rehearsal node");
16665
+ }
16486
16666
  const outputs = [];
16487
- const claims = mode === "rehearsal" || mode === "rehearsal-cleanup" ? mode === "rehearsal-cleanup" ? platformCommunityRehearsalDeletionClaims(keys) : platformCommunityRehearsalClaims(keys) : platformGenesisClaims(request.genesis.environment, keys);
16667
+ const claims = mode === "rehearsal" || mode === "rehearsal-cleanup" ? mode === "rehearsal-cleanup" ? platformCommunityRehearsalDeletionClaims(request.genesis.nodes, request.genesis.rehearsalNode) : platformCommunityRehearsalClaims(request.genesis.nodes, request.genesis.rehearsalNode, keys) : platformGenesisClaims(request.genesis.nodes, keys);
16488
16668
  for (const claim of claims) {
16489
16669
  outputs.push(await remote(exec, request, knownHosts, ["/usr/bin/sudo", "-n", "/usr/local/bin/fz-agent", "metal-apply", "--claim=-"], `provision ${claim.spec.guestName}`, false, `${JSON.stringify(claim)}
16490
16670
  `));
16491
16671
  }
16492
- return { plan, output: JSON.stringify({ environment: request.genesis.environment, nodes: plan.genesisNodes, outputs }) };
16672
+ return { plan, output: JSON.stringify({ nodes: plan.genesisNodes, outputs }) };
16493
16673
  }
16494
16674
  const config = readMetalBootstrapConfig(request.metalConfigFile);
16495
16675
  const staged = stageMetalConfig(config, directory);
@@ -16522,15 +16702,36 @@ async function applyOperatorMetalBootstrap(request, mode, options = {}) {
16522
16702
  }
16523
16703
 
16524
16704
  // src/platform-genesis-config.ts
16525
- function platformGenesisBootstrapConfigs(template, nodes) {
16526
- const environment = template.environment;
16527
- const fleet = PLATFORM_GENESIS_FLEETS[environment];
16528
- if (!fleet || nodes.length !== 3)
16529
- throw new Error("platform genesis requires exactly three selected-profile node coordinates");
16705
+ function platformGenesisBootstrapConfigs(template, guests, nodes, metalHostname, region) {
16706
+ const fleet = validatePlatformGenesisGuests(guests);
16707
+ if (!/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(metalHostname)) {
16708
+ throw new Error("platform genesis requires the reviewed metal hostname");
16709
+ }
16710
+ if (fleet.some(({ confidential }) => !confidential)) {
16711
+ throw new Error("platform genesis requires confidential compute");
16712
+ }
16713
+ if (nodes.length !== 3)
16714
+ throw new Error("platform genesis requires exactly three node coordinates");
16530
16715
  if (nodes.some((node) => !node.nodeHostname || !node.cloudflareHandoffFile)) {
16531
16716
  throw new Error("every platform genesis node requires its public hostname and node-specific Cloudflare handoff");
16532
16717
  }
16533
16718
  const coordinators = fleet.map((guest) => `http://${guest.address}:8529`);
16719
+ const initialInventory = {
16720
+ metalHostname,
16721
+ region: {
16722
+ key: template.runtime.environment.nodeRegion,
16723
+ label: region.label,
16724
+ country: region.country,
16725
+ ...region.city ? { city: region.city } : {},
16726
+ confidentialCapable: true
16727
+ },
16728
+ computes: fleet.map((guest, index) => ({
16729
+ ...guest,
16730
+ confidential: guest.confidential,
16731
+ databaseRole: index === 0 ? "master" : "joiner",
16732
+ databaseAgency: "member"
16733
+ }))
16734
+ };
16534
16735
  return fleet.map((guest, index) => {
16535
16736
  const node = nodes[index];
16536
16737
  const master = fleet[0].address;
@@ -16561,6 +16762,7 @@ function platformGenesisBootstrapConfigs(template, nodes) {
16561
16762
  nodeHostname: node.nodeHostname,
16562
16763
  seedSyncPeers: fleet.filter((_, peer) => peer !== index).map((peer) => `ws://${peer.address}:${config.runtime.bluePort}/internal/seed`),
16563
16764
  seedSyncMembers: 3,
16765
+ initialInventory,
16564
16766
  custodianEmail: index === 0 ? template.runtime.environment.custodianEmail : undefined
16565
16767
  };
16566
16768
  return validateBootstrapConfig(config);
@@ -17586,6 +17788,12 @@ var bootstrapNumber = (question, fallback) => {
17586
17788
  throw new Error(`${question} must be an integer`);
17587
17789
  return value;
17588
17790
  };
17791
+ var bootstrapBoolean = (question, fallback) => {
17792
+ const value = bootstrapAnswer(`${question} (yes/no)`, fallback).toLowerCase();
17793
+ if (value !== "yes" && value !== "no")
17794
+ throw new Error(`${question} must be yes or no`);
17795
+ return value === "yes";
17796
+ };
17589
17797
  async function bootstrapSecret(question) {
17590
17798
  if (!process.stdin.isTTY)
17591
17799
  throw new Error(`${question} requires an interactive terminal`);
@@ -17623,7 +17831,7 @@ var parseCpuPools = (value) => value.split(";").map((entry) => entry.trim()).fil
17623
17831
  function interactiveMetalBootstrap() {
17624
17832
  if (!process.stdin.isTTY)
17625
17833
  throw new Error("interactive metal config generation requires a terminal");
17626
- const confidential = bootstrapAnswer("Require SEV-SNP confidential guests? (yes/no)", "yes") === "yes";
17834
+ const confidential = bootstrapBoolean("Require SEV-SNP confidential guests?", "yes");
17627
17835
  const subnetPrefix = bootstrapAnswer("Guest IPv4 subnet prefix", "10.42.0");
17628
17836
  return validateMetalBootstrapConfig({
17629
17837
  kind: "metal",
@@ -17643,12 +17851,10 @@ function interactiveMetalBootstrap() {
17643
17851
  unitDir: "/etc/systemd/system",
17644
17852
  apiUrl: bootstrapAnswer("Platform public API URL"),
17645
17853
  agentTelemetryEndpoint: bootstrapAnswer("Public HTTPS Agent OTLP endpoint"),
17646
- images: {
17647
- [SUPPORTED_GUEST_IMAGE.key]: {
17648
- path: `/var/lib/forgezero/images/${SUPPORTED_GUEST_IMAGE.key}.img`,
17649
- sha256: SUPPORTED_GUEST_IMAGE.sha256
17650
- }
17651
- },
17854
+ images: Object.fromEntries(OS_CATALOG.filter(({ status }) => status === "active").map((image) => [
17855
+ image.imageKey,
17856
+ { path: `/var/lib/forgezero/images/${image.imageKey}.img`, sha256: image.imageSha256 }
17857
+ ])),
17652
17858
  cpuPools: parseCpuPools(bootstrapAnswer("CPU pools (key|cpus|physical-cores|memory-nodes;...)", "node1|8-15,72-79|8|1;node2|16-23,80-87|8|2;node3|24-31,88-95|8|3;node4|32-39,96-103|8|4;node5|40-47,104-111|8|5;node6|48-55,112-119|8|6;node7|56-63,120-127|8|7")),
17653
17859
  housekeepingCpus: bootstrapAnswer("Host housekeeping CPU list", "0-7,64-71"),
17654
17860
  housekeepingMemoryNodes: bootstrapAnswer("Host housekeeping memory nodes", "0"),
@@ -17683,7 +17889,7 @@ async function interactiveCloudflareBootstrap() {
17683
17889
  };
17684
17890
  const zoneName = bootstrapAnswer("Cloudflare DNS zone name", "forgezero.net");
17685
17891
  const kvNamespaceTitle = bootstrapAnswer("Existing Worker-bound KV namespace title");
17686
- const realtimeEnabled = bootstrapAnswer("Configure existing Worker realtime fan-out? (yes/no)", "yes") === "yes";
17892
+ const realtimeEnabled = bootstrapBoolean("Configure existing Worker realtime fan-out?", "yes");
17687
17893
  const workerScriptName = realtimeEnabled ? bootstrapAnswer("Existing Worker script name") : undefined;
17688
17894
  const discovered = await discoverCloudflareBootstrapCommandResources({
17689
17895
  zoneName,
@@ -17694,12 +17900,12 @@ async function interactiveCloudflareBootstrap() {
17694
17900
  const nodeCount = bootstrapNumber("Number of public API nodes", "3");
17695
17901
  if (nodeCount < 1 || nodeCount > 32)
17696
17902
  throw new Error("Number of public API nodes must be between 1 and 32");
17697
- const meshEnabled = bootstrapAnswer("Configure private Mesh/WARP routes? (yes/no)", "no") === "yes";
17903
+ const meshEnabled = bootstrapBoolean("Configure private Mesh/WARP routes?", "no");
17698
17904
  const meshDevicePolicyId = meshEnabled ? bootstrapAnswer("Existing Mesh device policy id") : undefined;
17699
17905
  const nodes = Array.from({ length: nodeCount }, (_, index) => {
17700
17906
  const ordinal = index + 1;
17701
17907
  const nodeName = bootstrapAnswer(`Node ${ordinal} inventory name`);
17702
- const nodeMesh = meshEnabled && bootstrapAnswer(`Configure Mesh routes for ${nodeName}? (yes/no)`, "yes") === "yes";
17908
+ const nodeMesh = meshEnabled && bootstrapBoolean(`Configure Mesh routes for ${nodeName}?`, "yes");
17703
17909
  return {
17704
17910
  nodeName,
17705
17911
  hostname: bootstrapAnswer(`${nodeName} public hostname`),
@@ -17709,7 +17915,7 @@ async function interactiveCloudflareBootstrap() {
17709
17915
  mesh: {
17710
17916
  connectorName: bootstrapAnswer(`${nodeName} Mesh connector name`, `${nodeName}-mesh`),
17711
17917
  routes: bootstrapList(`${nodeName} private CIDR routes`),
17712
- highAvailability: bootstrapAnswer(`${nodeName} shares a highly available Mesh connector? (yes/no)`, "no") === "yes"
17918
+ highAvailability: bootstrapBoolean(`${nodeName} shares a highly available Mesh connector?`, "no")
17713
17919
  }
17714
17920
  } : {}
17715
17921
  };
@@ -17756,6 +17962,28 @@ function operatorIdentityCoordinates() {
17756
17962
  agentSocket: bootstrapAnswer("Bitwarden SSH agent socket", process.env.SSH_AUTH_SOCK)
17757
17963
  };
17758
17964
  }
17965
+ function interactiveGenesisGuests() {
17966
+ const node = (index) => ({
17967
+ name: bootstrapAnswer(`Guest ${index} name`),
17968
+ address: bootstrapAnswer(`Guest ${index} private IPv4`),
17969
+ imageKey: bootstrapAnswer(`Guest ${index} OS image key`, SUPPORTED_GUEST_IMAGE.key),
17970
+ cpuPoolKey: bootstrapAnswer(`Guest ${index} exclusive CPU pool key`),
17971
+ physicalCores: bootstrapNumber(`Guest ${index} physical cores`),
17972
+ vcpu: bootstrapNumber(`Guest ${index} vCPUs`),
17973
+ memoryGib: bootstrapNumber(`Guest ${index} memory GiB`),
17974
+ diskGib: bootstrapNumber(`Guest ${index} disk GiB`),
17975
+ egressGuaranteedMbps: bootstrapNumber(`Guest ${index} guaranteed egress Mbps`),
17976
+ egressBurstMbps: bootstrapNumber(`Guest ${index} burst egress Mbps`),
17977
+ confidential: bootstrapBoolean(`Guest ${index} requires confidential SEV-SNP compute?`)
17978
+ });
17979
+ const nodes = [
17980
+ node(1),
17981
+ node(2),
17982
+ node(3)
17983
+ ];
17984
+ const rehearsalNode = bootstrapBoolean("Configure an optional fourth rehearsal guest?", "yes") ? node(4) : undefined;
17985
+ return { nodes, ...rehearsalNode ? { rehearsalNode } : {} };
17986
+ }
17759
17987
  function writeMetalOperatorFiles(directory) {
17760
17988
  const output = genesisOutputDirectory(directory);
17761
17989
  const config = interactiveMetalBootstrap();
@@ -17766,17 +17994,22 @@ function writeMetalOperatorFiles(directory) {
17766
17994
  kind: "metal-remote",
17767
17995
  metalConfigFile: metalConfig,
17768
17996
  target: { ...target, ...identity },
17769
- genesis: {
17770
- environment: bootstrapAnswer("Genesis environment (production/development)", "development"),
17771
- sshPublicKeyFiles: [identity.identityPublicKeyFile]
17772
- }
17997
+ genesis: { ...interactiveGenesisGuests(), sshPublicKeyFiles: [identity.identityPublicKeyFile] }
17773
17998
  });
17774
17999
  return { metalConfig, remoteRequest };
17775
18000
  }
17776
18001
  function writePlatformGenesisFleet(directory) {
17777
18002
  const output = genesisOutputDirectory(directory);
17778
- const template = interactiveBootstrap("platform", true);
17779
- const fleet = PLATFORM_GENESIS_FLEETS[template.environment];
18003
+ const metalRequestFile = bootstrapAnswer("Owner-only metal remote request containing the reviewed genesis guests");
18004
+ const metalRequest = readOperatorMetalBootstrapRequest(metalRequestFile);
18005
+ const fleet = metalRequest.genesis.nodes;
18006
+ const metalHostname = readMetalBootstrapConfig(metalRequest.metalConfigFile).metalHostname;
18007
+ const template = interactiveBootstrap("platform", fleet);
18008
+ const region = {
18009
+ label: bootstrapAnswer(`Region ${template.runtime.environment.nodeRegion} display label`),
18010
+ country: bootstrapAnswer("Region ISO 3166-1 alpha-2 country code").toUpperCase(),
18011
+ city: bootstrapAnswer("Region city")
18012
+ };
17780
18013
  const nodes = fleet.map((guest, index) => index === 0 ? {
17781
18014
  nodeHostname: template.nodeHostname,
17782
18015
  cloudflareHandoffFile: template.cloudflareHandoff.handoffFile
@@ -17784,13 +18017,13 @@ function writePlatformGenesisFleet(directory) {
17784
18017
  nodeHostname: bootstrapAnswer(`${guest.name} public node hostname`),
17785
18018
  cloudflareHandoffFile: bootstrapAnswer(`${guest.name} node-specific Cloudflare host handoff`)
17786
18019
  });
17787
- const configs = platformGenesisBootstrapConfigs(template, nodes).map((config) => {
18020
+ const configs = platformGenesisBootstrapConfigs(template, fleet, nodes, metalHostname, region).map((config) => {
17788
18021
  const path = `${output}/${config.computeReference}-platform.json`;
17789
18022
  writeBootstrapConfig(path, config);
17790
18023
  return path;
17791
18024
  });
17792
18025
  const identity = operatorIdentityCoordinates();
17793
- const useJump = bootstrapAnswer("Reach genesis computes through the Metal SSH jump? (yes/no)", "yes") === "yes";
18026
+ const useJump = bootstrapBoolean("Reach genesis computes through the Metal SSH jump?", "yes");
17794
18027
  const jump = useJump ? interactiveOperatorHop("Metal jump", { user: "root" }) : undefined;
17795
18028
  const requests = configs.map((platformConfigFile, index) => {
17796
18029
  const guest = fleet[index];
@@ -17804,14 +18037,15 @@ function writePlatformGenesisFleet(directory) {
17804
18037
  const fleetRequest = writeOperatorPlatformBootstrapFleetRequest(join12(output, "platform-fleet-remote.json"), requests);
17805
18038
  return { configs, requests, fleetRequest };
17806
18039
  }
17807
- function interactiveBootstrap(kind, genesis = false) {
18040
+ function interactiveBootstrap(kind, genesisGuests) {
17808
18041
  if (!process.stdin.isTTY)
17809
18042
  throw new Error("non-interactive bootstrap requires --bootstrap-config <private-json-file>");
17810
18043
  const environment = bootstrapAnswer("Environment (production/development)", "production");
17811
18044
  if (environment !== "production" && environment !== "development") {
17812
18045
  throw new Error("Environment must be production or development");
17813
18046
  }
17814
- const fleet = genesis ? PLATFORM_GENESIS_FLEETS[environment] : undefined;
18047
+ const genesis = genesisGuests !== undefined;
18048
+ const fleet = genesisGuests;
17815
18049
  const profile = genesis ? "platform-db-api" : bootstrapAnswer("Profile (platform-db-api/platform-api)", "platform-db-api");
17816
18050
  const role = genesis ? "master" : profile === "platform-api" ? "none" : bootstrapAnswer("Database role (master/joiner)", "master");
17817
18051
  const agency = genesis ? "member" : role === "master" ? "member" : role === "none" ? "none" : bootstrapAnswer("Agency participation (member/none)", "member");
@@ -17819,12 +18053,13 @@ function interactiveBootstrap(kind, genesis = false) {
17819
18053
  const address = genesis ? fleet[0].address : role === "none" ? undefined : bootstrapAnswer("Private database address");
17820
18054
  const master = role === "joiner" ? bootstrapAnswer("Master starter private address") : undefined;
17821
18055
  const coordinators = genesis ? fleet.map((guest) => `http://${guest.address}:8529`) : bootstrapAnswer("Writable Coordinator origins (comma-separated)").split(",").map((item) => item.trim()).filter(Boolean);
17822
- const computeReference = genesis ? fleet[0].name : bootstrapAnswer("API-owned compute reference", environment === "production" ? "fz-n1" : "dev-fz-n1");
18056
+ const computeReference = genesis ? fleet[0].name : bootstrapAnswer("API-owned compute reference");
17823
18057
  const nodeHostname = bootstrapAnswer(genesis ? `${computeReference} public node hostname` : "Public node hostname");
17824
18058
  const apiUrl = bootstrapAnswer("Platform API URL");
17825
18059
  const appOrigin = bootstrapAnswer("Public App origin");
17826
18060
  const bundleFile = bootstrapAnswer("Bootstrap API Git bundle (absolute path)");
17827
18061
  const manifestFile = bootstrapAnswer("Bootstrap bundle manifest (absolute path)", `${bundleFile}.json`);
18062
+ const bundleBranch = bootstrapAnswer("Bootstrap bundle Git branch");
17828
18063
  const telemetryEndpoint = bootstrapAnswer("Public HTTPS Agent OTLP endpoint");
17829
18064
  const collectorUnit = FORGEZERO_OTEL_COLLECTOR_UNIT;
17830
18065
  const replicationFactor = Number(bootstrapAnswer("Database replication factor", "3"));
@@ -17837,7 +18072,7 @@ function interactiveBootstrap(kind, genesis = false) {
17837
18072
  const smtpHost = emailProvider === "smtp" ? bootstrapAnswer("SMTP host") : undefined;
17838
18073
  const smtpPort = emailProvider === "smtp" ? Number(bootstrapAnswer("SMTP port", "587")) : undefined;
17839
18074
  const smtpUser = emailProvider === "smtp" ? bootstrapAnswer("SMTP user") : undefined;
17840
- const jetemailEu = emailProvider === "jetemail" ? bootstrapAnswer("Use JetEmail EU processing? (yes/no)", "no") === "yes" : undefined;
18075
+ const jetemailEu = emailProvider === "jetemail" ? bootstrapBoolean("Use JetEmail EU processing?", "no") : undefined;
17841
18076
  const backupEndpoint = bootstrapAnswer("Backup S3 HTTPS endpoint (blank to disable)", "");
17842
18077
  const backupRegion = backupEndpoint ? bootstrapAnswer("Backup S3 region") : undefined;
17843
18078
  const backupBucket = backupEndpoint ? bootstrapAnswer("Backup S3 bucket") : undefined;
@@ -17853,7 +18088,7 @@ function interactiveBootstrap(kind, genesis = false) {
17853
18088
  computeReference,
17854
18089
  nodeHostname,
17855
18090
  apiUrl,
17856
- bootstrapBundle: { bundleFile, manifestFile },
18091
+ bootstrapBundle: { bundleFile, manifestFile, branch: bundleBranch },
17857
18092
  telemetryEndpoint,
17858
18093
  database: {
17859
18094
  role,
@@ -17907,12 +18142,12 @@ function interactiveBootstrap(kind, genesis = false) {
17907
18142
  keepReleases: 5
17908
18143
  },
17909
18144
  firewall: {
17910
- enabled: bootstrapAnswer("Enable host firewall? (yes/no)", "yes") === "yes",
18145
+ enabled: bootstrapBoolean("Enable host firewall?", "yes"),
17911
18146
  sshPort: Number(bootstrapAnswer("SSH port", "22")),
17912
18147
  privateCidrs: bootstrapAnswer("Private cluster CIDRs (comma-separated)").split(",").map((item) => item.trim()).filter(Boolean)
17913
18148
  },
17914
18149
  installCloudflared: Boolean(cloudflareHandoffFile),
17915
- installWarp: !genesis && cloudflareHandoffFile ? bootstrapAnswer("Configure Cloudflare Mesh/WARP from this handoff? (yes/no)", "no") === "yes" : false,
18150
+ installWarp: !genesis && cloudflareHandoffFile ? bootstrapBoolean("Configure Cloudflare Mesh/WARP from this handoff?", "no") : false,
17916
18151
  ...cloudflareHandoffFile && cloudflareNodeName ? {
17917
18152
  cloudflareHandoff: { handoffFile: cloudflareHandoffFile, nodeName: cloudflareNodeName }
17918
18153
  } : {}
@@ -18501,9 +18736,9 @@ async function cmdDeploy(options, args) {
18501
18736
  if (existsSync12(resolve11(options.projectRoot, DEPLOY_SOURCE_FILE))) {
18502
18737
  const expected = await compileDeploymentProject(options.projectRoot, { write: false });
18503
18738
  const actual = inspectCompiledDeployment(options.projectRoot);
18504
- const current = canonicalJson(expected.plan) === canonicalJson(actual.plan);
18739
+ const current2 = canonicalJson(expected.plan) === canonicalJson(actual.plan);
18505
18740
  const problems = [
18506
- ...current ? [] : [`${DEPLOY_PLAN_FILE} is stale; run \`fz deploy compile\``],
18741
+ ...current2 ? [] : [`${DEPLOY_PLAN_FILE} is stale; run \`fz deploy compile\``],
18507
18742
  ...canonicalJson(expected.plan).includes("pipeline-todo") ? ["typed plan contains safe initialization blockers"] : []
18508
18743
  ];
18509
18744
  const summary = { source: expected.source, output: actual.path, digest: actual.digest, version: actual.plan.version, name: actual.plan.name, ready: problems.length === 0, problems };
@@ -18723,8 +18958,9 @@ function usage() {
18723
18958
  Verify recovered data and bounded cluster topology
18724
18959
  fz db consolidate Plan/apply a digest-bound offline schema consolidation
18725
18960
  fz rehearse community-cluster-api
18726
- Plan the fixed four-node Community 3.11.14 rehearsal;
18727
- --apply also requires its exact disposable-fleet confirmation
18961
+ Plan the reviewed four-node Community 3.11.14 rehearsal;
18962
+ requires --request=<metal-remote.json>; mutation also requires
18963
+ the exact disposable-fleet confirmation printed by the plan
18728
18964
  fz rehearse platform-fleet
18729
18965
  Verify a development or production fleet; use --plan offline
18730
18966
  fz rehearse platform-recovery