@forgezero/agent 0.1.65 → 0.1.68

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.
@@ -749,7 +749,7 @@ async function postSignedNode(options, path, body) {
749
749
  }
750
750
 
751
751
  // src/version.ts
752
- var VERSION3 = "0.1.65";
752
+ var VERSION3 = "0.1.68";
753
753
 
754
754
  // src/agent-heartbeat.ts
755
755
  var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
@@ -26,6 +26,7 @@ export interface BootstrapBundleCommandResult {
26
26
  }
27
27
  export type BootstrapBundleCommand = (argv: readonly string[]) => Promise<BootstrapBundleCommandResult>;
28
28
  export declare function sha256File(path: string): Promise<string>;
29
+ export declare const bootstrapBundleBranch: (value: unknown) => string;
29
30
  export declare function parseBootstrapBundleManifest(value: unknown): BootstrapBundleManifest;
30
31
  export declare function readBootstrapBundle(bundlePath: string, manifestPath?: string): Promise<BootstrapBundleBuildResult>;
31
32
  export declare function buildBootstrapBundle(input: BootstrapBundleBuildInput, exec?: BootstrapBundleCommand): Promise<BootstrapBundleBuildResult>;
@@ -41,7 +41,7 @@ async function sha256File(path) {
41
41
  });
42
42
  return hash.digest("hex");
43
43
  }
44
- var branchName = (value) => {
44
+ var bootstrapBundleBranch = (value) => {
45
45
  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("-")) {
46
46
  throw new Error("bootstrap bundle branch is malformed");
47
47
  }
@@ -58,7 +58,7 @@ function parseBootstrapBundleManifest(value) {
58
58
  if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
59
59
  throw new Error("bootstrap bundle manifest format is unsupported");
60
60
  }
61
- branchName(source.branch);
61
+ bootstrapBundleBranch(source.branch);
62
62
  if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
63
63
  throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
64
64
  }
@@ -103,7 +103,7 @@ async function buildBootstrapBundle(input, exec = run) {
103
103
  const repositoryRoot = resolve(input.repositoryRoot);
104
104
  const outputPath = resolve(input.outputPath);
105
105
  const manifestPath = `${outputPath}.json`;
106
- const branch = branchName(input.branch);
106
+ const branch = bootstrapBundleBranch(input.branch);
107
107
  if (!isAbsolute(input.outputPath) || outputPath !== input.outputPath) {
108
108
  throw new Error("bootstrap bundle output must be a canonical absolute path");
109
109
  }
@@ -159,6 +159,7 @@ export {
159
159
  readBootstrapBundle,
160
160
  parseBootstrapBundleManifest,
161
161
  buildBootstrapBundle,
162
+ bootstrapBundleBranch,
162
163
  MAX_BOOTSTRAP_BUNDLE_BYTES,
163
164
  BOOTSTRAP_BUNDLE_KIND,
164
165
  BOOTSTRAP_BUNDLE_FORMAT
@@ -13,7 +13,7 @@ export interface PlatformBootstrapConfig {
13
13
  kind: 'platform';
14
14
  environment: BootstrapEnvironment;
15
15
  profile: PlatformBootstrapProfile;
16
- /** API-owned compute reference. Initial seed references are fz-n1..3/dev-fz-n1..3. */
16
+ /** API-owned compute reference. Initial seed references come from the reviewed generic guest requests. */
17
17
  computeReference: string;
18
18
  nodeHostname: string;
19
19
  apiUrl: string;
@@ -21,6 +21,7 @@ export interface PlatformBootstrapConfig {
21
21
  bootstrapBundle: {
22
22
  bundleFile: string;
23
23
  manifestFile: string;
24
+ branch: string;
24
25
  };
25
26
  deployRoot?: string;
26
27
  telemetryEndpoint: string;
@@ -70,6 +71,11 @@ export interface PlatformBootstrapConfig {
70
71
  }
71
72
  export interface EnrolledComputeActivationConfig {
72
73
  kind: 'enrolled-compute';
74
+ operatingSystem: {
75
+ id: 'ubuntu';
76
+ version: '24.04' | '26.04';
77
+ architecture: 'x64';
78
+ };
73
79
  apiUrl: string;
74
80
  realm: string;
75
81
  nodeHostname: string;
package/dist/bootstrap.js CHANGED
@@ -1251,7 +1251,7 @@ async function sha256File(path) {
1251
1251
  });
1252
1252
  return hash.digest("hex");
1253
1253
  }
1254
- var branchName = (value) => {
1254
+ var bootstrapBundleBranch = (value) => {
1255
1255
  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("-")) {
1256
1256
  throw new Error("bootstrap bundle branch is malformed");
1257
1257
  }
@@ -1268,7 +1268,7 @@ function parseBootstrapBundleManifest(value) {
1268
1268
  if (source.format !== BOOTSTRAP_BUNDLE_FORMAT || source.kind !== BOOTSTRAP_BUNDLE_KIND) {
1269
1269
  throw new Error("bootstrap bundle manifest format is unsupported");
1270
1270
  }
1271
- branchName(source.branch);
1271
+ bootstrapBundleBranch(source.branch);
1272
1272
  if (typeof source.revision !== "string" || !/^[a-f0-9]{40}$/.test(source.revision)) {
1273
1273
  throw new Error("bootstrap bundle revision must be an exact lowercase Git commit");
1274
1274
  }
@@ -1313,7 +1313,7 @@ async function buildBootstrapBundle(input, exec = run) {
1313
1313
  const repositoryRoot = resolve2(input.repositoryRoot);
1314
1314
  const outputPath = resolve2(input.outputPath);
1315
1315
  const manifestPath = `${outputPath}.json`;
1316
- const branch = branchName(input.branch);
1316
+ const branch = bootstrapBundleBranch(input.branch);
1317
1317
  if (!isAbsolute(input.outputPath) || outputPath !== input.outputPath) {
1318
1318
  throw new Error("bootstrap bundle output must be a canonical absolute path");
1319
1319
  }
@@ -1396,11 +1396,37 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1396
1396
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1397
1397
 
1398
1398
  // src/version.ts
1399
- var VERSION = "0.1.65";
1399
+ var VERSION = "0.1.68";
1400
1400
 
1401
1401
  // src/software.ts
1402
1402
  var PINNED_BUN_VERSION = "1.3.14";
1403
1403
  var BUN_RELEASE_SHA256 = "951ee2aee855f08595aeec6225226a298d3fea83a3dcd6465c09cbccdf7e848f";
1404
+ var OS_CATALOG = [
1405
+ {
1406
+ id: "ubuntu",
1407
+ version: "26.04",
1408
+ architecture: "x64",
1409
+ status: "active",
1410
+ imageKey: "ubuntu-resolute-20260731",
1411
+ imageLabel: "Ubuntu 26.04 LTS Resolute",
1412
+ imageVersion: "2026-07-31",
1413
+ imageUrl: "https://cloud-images.ubuntu.com/releases/resolute/release-20260731/ubuntu-26.04-server-cloudimg-amd64.img",
1414
+ imageSha256: "9dc7c5363c0146a08ba0c9aa834d82c2c6dfbb1c471ad9a2f0aba1189e21be05",
1415
+ confidentialModes: ["sev-snp"]
1416
+ },
1417
+ {
1418
+ id: "ubuntu",
1419
+ version: "24.04",
1420
+ architecture: "x64",
1421
+ status: "active",
1422
+ imageKey: "ubuntu-noble-20260705",
1423
+ imageLabel: "Ubuntu 24.04 LTS Noble",
1424
+ imageVersion: "2026-07-05",
1425
+ imageUrl: "https://cloud-images.ubuntu.com/releases/noble/release-20260705/ubuntu-24.04-server-cloudimg-amd64.img",
1426
+ imageSha256: "ffe6203da54deeb6db5d2a98a83f9ec8e55f149d3f7ba622e1abe5fa966ee3d6",
1427
+ confidentialModes: []
1428
+ }
1429
+ ];
1404
1430
  var DOCKER_DAEMON_CONFIG = `${JSON.stringify({
1405
1431
  "data-root": "/var/lib/docker",
1406
1432
  "storage-driver": "overlay2",
@@ -2786,6 +2812,143 @@ async function applyPlan(plan, run2) {
2786
2812
  return transcript;
2787
2813
  }
2788
2814
 
2815
+ // src/ubuntu.ts
2816
+ var current = OS_CATALOG[0];
2817
+ var SUPPORTED_GUEST_IMAGE = Object.freeze({
2818
+ key: current.imageKey,
2819
+ family: `${current.id}-${current.version}`,
2820
+ version: current.imageVersion,
2821
+ label: current.imageLabel,
2822
+ url: current.imageUrl,
2823
+ sha256: current.imageSha256
2824
+ });
2825
+ var SUPPORTED_GUEST_IMAGES = Object.freeze(OS_CATALOG.filter(({ status }) => status === "active").map(({ id, imageKey, imageLabel, imageVersion, version, confidentialModes }) => ({
2826
+ key: imageKey,
2827
+ label: imageLabel,
2828
+ family: `${id}-${version}`,
2829
+ version: imageVersion,
2830
+ confidential: confidentialModes.some((mode) => mode === "sev-snp")
2831
+ })));
2832
+ function assertSupportedGuestImage(imageKey, confidential = false) {
2833
+ const selected = OS_CATALOG.find((entry) => entry.status === "active" && entry.imageKey === imageKey);
2834
+ if (!selected) {
2835
+ throw new Error(`unsupported guest image ${imageKey}; supported images: ${SUPPORTED_GUEST_IMAGES.map(({ key }) => key).join(", ")}`);
2836
+ }
2837
+ if (confidential && !selected.confidentialModes.some((mode) => mode === "sev-snp")) {
2838
+ throw new Error(`guest image ${imageKey} is not approved for SEV-SNP`);
2839
+ }
2840
+ }
2841
+
2842
+ // src/platform-genesis.ts
2843
+ var SAFE_NAME = /^[a-z][a-z0-9-]{0,62}$/;
2844
+ var SAFE_POOL = /^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$/;
2845
+ 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}))$/;
2846
+ var integer = (value, minimum, maximum, label) => {
2847
+ if (!Number.isInteger(value) || value < minimum || value > maximum) {
2848
+ throw new Error(`platform genesis ${label} is outside the supported range`);
2849
+ }
2850
+ return value;
2851
+ };
2852
+ function validatePlatformGenesisGuests(guests, expectedCount = 3) {
2853
+ if (!Array.isArray(guests) || guests.length !== expectedCount) {
2854
+ throw new Error(`platform genesis requires exactly ${expectedCount} reviewed guest requests`);
2855
+ }
2856
+ const names = new Set;
2857
+ const addresses = new Set;
2858
+ const pools = new Set;
2859
+ return guests.map((input) => {
2860
+ if (!input || typeof input !== "object" || !SAFE_NAME.test(input.name)) {
2861
+ throw new Error("platform genesis guest name is malformed");
2862
+ }
2863
+ if (!PRIVATE_V4.test(input.address))
2864
+ throw new Error("platform genesis guest address must be private IPv4");
2865
+ if (typeof input.confidential !== "boolean")
2866
+ throw new Error("platform genesis confidential requirement is missing");
2867
+ assertSupportedGuestImage(input.imageKey, input.confidential);
2868
+ if (input.cpuPoolKey !== undefined && !SAFE_POOL.test(input.cpuPoolKey)) {
2869
+ throw new Error("platform genesis CPU pool key is malformed");
2870
+ }
2871
+ if (names.has(input.name) || addresses.has(input.address) || input.cpuPoolKey !== undefined && pools.has(input.cpuPoolKey)) {
2872
+ throw new Error("platform genesis guest names, addresses and explicit CPU pools must be unique");
2873
+ }
2874
+ names.add(input.name);
2875
+ addresses.add(input.address);
2876
+ if (input.cpuPoolKey !== undefined)
2877
+ pools.add(input.cpuPoolKey);
2878
+ const physicalCores = integer(input.physicalCores, 1, 256, "physical core count");
2879
+ const vcpu = integer(input.vcpu, 1, 512, "vCPU count");
2880
+ if (vcpu < physicalCores)
2881
+ throw new Error("platform genesis vCPU count cannot be smaller than physical cores");
2882
+ const memoryGib = integer(input.memoryGib, 1, 8192, "memory");
2883
+ const diskGib = integer(input.diskGib, 8, 65536, "disk");
2884
+ const egressGuaranteedMbps = integer(input.egressGuaranteedMbps, 0, 1e6, "guaranteed egress");
2885
+ const egressBurstMbps = integer(input.egressBurstMbps, egressGuaranteedMbps, 1e6, "burst egress");
2886
+ return {
2887
+ name: input.name,
2888
+ address: input.address,
2889
+ imageKey: input.imageKey,
2890
+ ...input.cpuPoolKey ? { cpuPoolKey: input.cpuPoolKey } : {},
2891
+ physicalCores,
2892
+ vcpu,
2893
+ memoryGib,
2894
+ diskGib,
2895
+ egressGuaranteedMbps,
2896
+ egressBurstMbps,
2897
+ confidential: input.confidential
2898
+ };
2899
+ });
2900
+ }
2901
+ var publicKey = (value) => {
2902
+ const normalized = value.trim();
2903
+ if (!/^ssh-(?:ed25519|rsa) [A-Za-z0-9+/]+={0,3}(?: [^\r\n]+)?$/.test(normalized)) {
2904
+ throw new Error("platform genesis SSH public key is malformed");
2905
+ }
2906
+ return normalized;
2907
+ };
2908
+ var claimFor = (guest, keys, action) => {
2909
+ const common = {
2910
+ computeKey: `platform:${guest.name}`,
2911
+ claimToken: "offline-platform-genesis",
2912
+ claimExpiresAtTs: Number.MAX_SAFE_INTEGER,
2913
+ attempt: 1,
2914
+ bootstrap: { kind: "platform-genesis" },
2915
+ spec: {
2916
+ reference: `platform:${guest.name}`,
2917
+ imageKey: guest.imageKey,
2918
+ guestName: guest.name,
2919
+ guestAddress: guest.address,
2920
+ ...guest.cpuPoolKey ? { cpuPoolKey: guest.cpuPoolKey } : {},
2921
+ physicalCores: guest.physicalCores,
2922
+ vcpu: guest.vcpu,
2923
+ memoryGib: guest.memoryGib,
2924
+ diskGib: guest.diskGib,
2925
+ egressGuaranteedMbps: guest.egressGuaranteedMbps,
2926
+ egressBurstMbps: guest.egressBurstMbps,
2927
+ confidential: guest.confidential
2928
+ }
2929
+ };
2930
+ if (action === "delete")
2931
+ return { ...common, action: "delete" };
2932
+ return { ...common, action: "create", access: { sshUser: "forgezero", sshPublicKeys: [...keys] } };
2933
+ };
2934
+ function platformGenesisClaims(guests, sshPublicKeys) {
2935
+ const reviewed = validatePlatformGenesisGuests(guests);
2936
+ const keys = [...new Set(sshPublicKeys.map(publicKey))];
2937
+ if (keys.length === 0)
2938
+ throw new Error("platform genesis requires at least one operator SSH public key");
2939
+ return reviewed.map((guest) => claimFor(guest, keys, "create"));
2940
+ }
2941
+ function platformCommunityRehearsalClaims(seedGuests, rehearsalGuest, sshPublicKeys) {
2942
+ const reviewed = validatePlatformGenesisGuests([...seedGuests, rehearsalGuest], 4);
2943
+ const keys = [...new Set(sshPublicKeys.map(publicKey))];
2944
+ if (keys.length === 0)
2945
+ throw new Error("platform genesis requires at least one operator SSH public key");
2946
+ return reviewed.map((guest) => claimFor(guest, keys, "create"));
2947
+ }
2948
+ function platformCommunityRehearsalDeletionClaims(seedGuests, rehearsalGuest) {
2949
+ return validatePlatformGenesisGuests([...seedGuests, rehearsalGuest], 4).map((guest) => claimFor(guest, [], "delete"));
2950
+ }
2951
+
2789
2952
  // src/platform-bootstrap-runtime.ts
2790
2953
  var safeAtom = (name, value) => {
2791
2954
  if (!value || /[\0\r\n]/.test(value))
@@ -2817,6 +2980,50 @@ var httpsOrigin = (name, raw) => {
2817
2980
  }
2818
2981
  return value.origin;
2819
2982
  };
2983
+ function validatePlatformInitialInventory(value) {
2984
+ 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)) {
2985
+ throw new Error("initial platform inventory coordinates are invalid");
2986
+ }
2987
+ safeAtom("initialInventory.region.label", value.region.label);
2988
+ if (value.region.city !== undefined)
2989
+ safeAtom("initialInventory.region.city", value.region.city);
2990
+ const computes = validatePlatformGenesisGuests(value.computes.map((compute) => {
2991
+ if (!compute || typeof compute !== "object" || Array.isArray(compute) || Object.keys(compute).some((key) => ![
2992
+ "name",
2993
+ "address",
2994
+ "imageKey",
2995
+ "cpuPoolKey",
2996
+ "physicalCores",
2997
+ "vcpu",
2998
+ "memoryGib",
2999
+ "diskGib",
3000
+ "egressGuaranteedMbps",
3001
+ "egressBurstMbps",
3002
+ "confidential",
3003
+ "databaseRole",
3004
+ "databaseAgency"
3005
+ ].includes(key)))
3006
+ throw new Error("initial platform compute contains unknown fields");
3007
+ return compute;
3008
+ }));
3009
+ for (let index = 0;index < computes.length; index += 1) {
3010
+ const declared = value.computes[index];
3011
+ const expectedRole = index === 0 ? "master" : "joiner";
3012
+ if (declared.databaseRole !== expectedRole || declared.databaseAgency !== "member" || declared.confidential !== true) {
3013
+ throw new Error("initial platform inventory requires one master, two joiners, three Agency members and confidential compute");
3014
+ }
3015
+ }
3016
+ return {
3017
+ metalHostname: value.metalHostname,
3018
+ region: { ...value.region },
3019
+ computes: value.computes.map((compute, index) => ({
3020
+ ...computes[index],
3021
+ databaseRole: compute.databaseRole,
3022
+ databaseAgency: compute.databaseAgency,
3023
+ confidential: true
3024
+ }))
3025
+ };
3026
+ }
2820
3027
  var systemdValue = (name, raw) => {
2821
3028
  if (/[\0\r\n]/.test(raw))
2822
3029
  throw new Error(`${name} must be single-line.`);
@@ -2902,12 +3109,14 @@ function validatePlatformSharedEnvironment(input) {
2902
3109
  throw new Error("realtime.producer is invalid.");
2903
3110
  }
2904
3111
  }
3112
+ const initialInventory = input.initialInventory ? validatePlatformInitialInventory(input.initialInventory) : undefined;
2905
3113
  return {
2906
3114
  ...input,
2907
3115
  databaseCoordinators: coordinators,
2908
3116
  appOrigin: httpsOrigin("appOrigin", input.appOrigin),
2909
3117
  apiOrigin: httpsOrigin("apiOrigin", input.apiOrigin),
2910
- agentOtlpEndpoint: httpsOrigin("agentOtlpEndpoint", input.agentOtlpEndpoint)
3118
+ agentOtlpEndpoint: httpsOrigin("agentOtlpEndpoint", input.agentOtlpEndpoint),
3119
+ ...initialInventory ? { initialInventory } : {}
2911
3120
  };
2912
3121
  }
2913
3122
  function renderPlatformSharedEnvironment(input) {
@@ -2970,7 +3179,8 @@ function renderPlatformSharedEnvironment(input) {
2970
3179
  FZ_CF_TUNNEL_SERVICE: value.cloudflare?.tunnelService ?? "",
2971
3180
  FZ_REALTIME_WORKER_SCRIPT: value.realtime?.workerScriptName ?? "",
2972
3181
  FZ_REALTIME_ENDPOINT: value.realtime?.endpoint ?? "",
2973
- FZ_REALTIME_PRODUCER: value.realtime?.producer ?? ""
3182
+ FZ_REALTIME_PRODUCER: value.realtime?.producer ?? "",
3183
+ FZ_PLATFORM_INITIAL_INVENTORY: value.initialInventory ? JSON.stringify(value.initialInventory) : ""
2974
3184
  };
2975
3185
  return `# Generated by fz bootstrap platform. Non-secret coordinates only.
2976
3186
  ` + Object.entries(entries).map(([key, entry]) => `${key}=${systemdValue(key, entry)}`).join(`
@@ -3485,6 +3695,12 @@ function validateBootstrapConfig(value) {
3485
3695
  throw new Error("Cloudflare Mesh/WARP requires a node-specific Cloudflare handoff");
3486
3696
  }
3487
3697
  if (value.kind === "enrolled-compute") {
3698
+ const os = OS_CATALOG.find((entry) => entry.status === "active" && entry.id === value.operatingSystem?.id && entry.version === value.operatingSystem?.version && entry.architecture === value.operatingSystem?.architecture);
3699
+ if (!os)
3700
+ throw new Error("enrolled compute operating system is not in the active typed OS catalog");
3701
+ if (value.operatingSystem.version === "24.04" && (value.installCloudflared || value.installWarp)) {
3702
+ throw new Error("Cloudflare Tunnel and WARP require the reviewed Ubuntu 26.04 strategy");
3703
+ }
3488
3704
  if (value.gitDeployKey !== undefined && typeof value.gitDeployKey !== "boolean") {
3489
3705
  throw new Error("gitDeployKey must be boolean");
3490
3706
  }
@@ -3516,6 +3732,7 @@ function validateBootstrapConfig(value) {
3516
3732
  if (!value.bootstrapBundle || ![value.bootstrapBundle.bundleFile, value.bootstrapBundle.manifestFile].every((path) => typeof path === "string" && path.startsWith("/") && !/[\r\n\0:]/.test(path))) {
3517
3733
  throw new Error("platform bootstrap requires absolute bundle and manifest paths");
3518
3734
  }
3735
+ bootstrapBundleBranch(value.bootstrapBundle.branch);
3519
3736
  let api;
3520
3737
  try {
3521
3738
  api = new URL(value.apiUrl);
@@ -3570,9 +3787,19 @@ function validateBootstrapConfig(value) {
3570
3787
  }
3571
3788
  function planBootstrap(input, initialized = false) {
3572
3789
  const config = validateBootstrapConfig(structuredClone(input));
3790
+ const enrolledOsVersion = config.kind === "enrolled-compute" ? `ubuntu-${config.operatingSystem.version}` : undefined;
3791
+ const enrolledSoftware = config.kind === "enrolled-compute" ? [...config.software ?? []] : [];
3792
+ if (config.kind === "enrolled-compute") {
3793
+ if (!enrolledSoftware.some(({ id }) => id === "bun"))
3794
+ enrolledSoftware.push({ id: "bun", version: "1.3.14" });
3795
+ if (!enrolledSoftware.some(({ id }) => id === "git"))
3796
+ enrolledSoftware.push({ id: "git", version: enrolledOsVersion });
3797
+ if (!enrolledSoftware.some(({ id }) => id === "ufw"))
3798
+ enrolledSoftware.push({ id: "ufw", version: enrolledOsVersion });
3799
+ }
3573
3800
  const software = config.kind === "enrolled-compute" ? [
3574
- ...config.software ?? [],
3575
- ...config.bootstrapRunner && !config.software?.some(({ id }) => id === "openssh-client") ? [{ id: "openssh-client", version: "ubuntu-26.04" }] : [],
3801
+ ...enrolledSoftware,
3802
+ ...config.bootstrapRunner && !enrolledSoftware.some(({ id }) => id === "openssh-client") ? [{ id: "openssh-client", version: enrolledOsVersion }] : [],
3576
3803
  ...config.installCloudflared ? [{ id: "cloudflared", version: "2026.7.3" }] : [],
3577
3804
  ...config.installWarp ? [{ id: "cloudflare-warp", version: "2026.6.822.0-min" }] : []
3578
3805
  ] : [
@@ -3807,9 +4034,8 @@ async function verifyBootstrapBundleOnHost(host, config, verifyGit = false) {
3807
4034
  throw new Error("bootstrap bundle manifest is not valid JSON");
3808
4035
  }
3809
4036
  const manifest = parseBootstrapBundleManifest(parsed);
3810
- const expectedBranch = config.environment === "production" ? "main" : "dev";
3811
- if (manifest.branch !== expectedBranch || bundleMetadata && bundleMetadata.size !== manifest.bytes) {
3812
- throw new Error("bootstrap bundle manifest disagrees with the selected platform environment");
4037
+ if (manifest.branch !== config.bootstrapBundle.branch || bundleMetadata && bundleMetadata.size !== manifest.bytes) {
4038
+ throw new Error("bootstrap bundle manifest disagrees with the reviewed bootstrap bundle coordinates");
3813
4039
  }
3814
4040
  const digest = (await checked3(host, ["/usr/bin/sha256sum", config.bootstrapBundle.bundleFile], "bootstrap bundle digest")).trim().split(/\s+/)[0];
3815
4041
  if (digest !== manifest.sha256)
@@ -3823,6 +4049,7 @@ function bootstrapIdentity(config) {
3823
4049
  if (config.kind === "enrolled-compute")
3824
4050
  return {
3825
4051
  kind: config.kind,
4052
+ operatingSystem: config.operatingSystem,
3826
4053
  apiUrl: config.apiUrl,
3827
4054
  realm: config.realm,
3828
4055
  nodeHostname: config.nodeHostname,
@@ -4436,7 +4663,7 @@ function strictBootstrapDocument(value) {
4436
4663
  "bootstrapRunner"
4437
4664
  ], "bootstrap config");
4438
4665
  if (root.kind === "platform") {
4439
- exactKeys(root.bootstrapBundle, ["bundleFile", "manifestFile"], "bootstrap bundle config");
4666
+ exactKeys(root.bootstrapBundle, ["bundleFile", "manifestFile", "branch"], "bootstrap bundle config");
4440
4667
  exactKeys(root.firewall, ["enabled", "sshPort", "privateCidrs"], "firewall config");
4441
4668
  if (root.cloudflareHandoff !== undefined)
4442
4669
  exactKeys(root.cloudflareHandoff, ["handoffFile", "nodeName"], "Cloudflare handoff");
@@ -4483,9 +4710,32 @@ function strictBootstrapDocument(value) {
4483
4710
  "otlpTraceSampleRatio",
4484
4711
  "backup",
4485
4712
  "cloudflare",
4486
- "realtime"
4713
+ "realtime",
4714
+ "initialInventory"
4487
4715
  ], "runtime environment");
4488
4716
  const environment = runtime.environment;
4717
+ if (environment.initialInventory !== undefined) {
4718
+ const inventory = exactKeys(environment.initialInventory, ["metalHostname", "region", "computes"], "initial inventory");
4719
+ exactKeys(inventory.region, ["key", "label", "country", "city", "confidentialCapable"], "initial inventory region");
4720
+ if (!Array.isArray(inventory.computes))
4721
+ throw new Error("initial inventory computes must be an array");
4722
+ for (const compute of inventory.computes)
4723
+ exactKeys(compute, [
4724
+ "name",
4725
+ "address",
4726
+ "imageKey",
4727
+ "cpuPoolKey",
4728
+ "physicalCores",
4729
+ "vcpu",
4730
+ "memoryGib",
4731
+ "diskGib",
4732
+ "egressGuaranteedMbps",
4733
+ "egressBurstMbps",
4734
+ "confidential",
4735
+ "databaseRole",
4736
+ "databaseAgency"
4737
+ ], "initial inventory compute");
4738
+ }
4489
4739
  if (environment.email !== undefined) {
4490
4740
  const email = exactKeys(environment.email, ["provider", "host", "port", "user", "from", "eu"], "email config");
4491
4741
  if (email.provider === "smtp")
@@ -4502,6 +4752,7 @@ function strictBootstrapDocument(value) {
4502
4752
  if (environment.realtime !== undefined)
4503
4753
  exactKeys(environment.realtime, ["workerScriptName", "endpoint", "producer"], "realtime runtime config");
4504
4754
  } else if (root.kind === "enrolled-compute") {
4755
+ exactKeys(root.operatingSystem, ["id", "version", "architecture"], "enrolled compute operating system");
4505
4756
  if (root.cloudflareHandoff !== undefined)
4506
4757
  exactKeys(root.cloudflareHandoff, ["handoffFile", "nodeName"], "Cloudflare handoff");
4507
4758
  if (root.bootstrapRunner !== undefined)
@@ -4,12 +4,20 @@ export declare const COMMUNITY_REHEARSAL_ROOT = "/opt/forgezero-rehearsals/commu
4
4
  export declare const COMMUNITY_DATABASE_ROOT = "/var/lib/forgezero-rehearsal-cluster";
5
5
  export declare const COMMUNITY_CREDENTIAL_PARENT = "/etc/forgezero-rehearsal";
6
6
  export declare const COMMUNITY_CREDENTIAL_ROOT = "/etc/forgezero-rehearsal/creds";
7
+ export interface CommunityRehearsalNode {
8
+ name: string;
9
+ address: string;
10
+ agency: boolean;
11
+ starter: boolean;
12
+ readPreferred: boolean;
13
+ }
7
14
  export type CommunityRehearsalApiOperation = 'health' | 'init' | 'write' | 'read' | 'query' | 'cluster';
8
15
  export type CommunityRehearsalHostRequest = {
9
16
  action: 'prepare';
10
17
  node: string;
11
18
  address: string;
12
19
  agency: boolean;
20
+ topology: readonly CommunityRehearsalNode[];
13
21
  release: string;
14
22
  archivePath: string;
15
23
  archiveSha256: string;
@@ -17,12 +25,15 @@ export type CommunityRehearsalHostRequest = {
17
25
  } | {
18
26
  action: 'cleanup';
19
27
  node: string;
28
+ topology: readonly CommunityRehearsalNode[];
20
29
  } | {
21
30
  action: 'database-enable' | 'api-enable' | 'database-ready' | 'starter-ready' | 'api-ready' | 'database-status' | 'api-status';
22
31
  node: string;
32
+ topology: readonly CommunityRehearsalNode[];
23
33
  } | {
24
34
  action: 'api';
25
35
  node: string;
36
+ topology: readonly CommunityRehearsalNode[];
26
37
  operation: CommunityRehearsalApiOperation;
27
38
  key?: string;
28
39
  value?: string;
@@ -49,6 +60,7 @@ export type CommunityHostOperation = {
49
60
  accepted?: readonly number[];
50
61
  };
51
62
  export declare function ensureCommunityRehearsalArango(execute?: SoftwareExec): Promise<void>;
63
+ export declare const validateCommunityRehearsalTopology: (value: unknown) => readonly CommunityRehearsalNode[];
52
64
  export declare function parseCommunityRehearsalHostRequest(value: unknown): CommunityRehearsalHostRequest;
53
65
  export declare function planCommunityRehearsalPrepare(request: Extract<CommunityRehearsalHostRequest, {
54
66
  action: 'prepare';