@forgezero/agent 0.1.128 → 0.1.129

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/README.md CHANGED
@@ -214,7 +214,7 @@ fz unlock --phrase-file /secure/offline-phrase.txt
214
214
 
215
215
  ## Use website operations from a project or an AI agent
216
216
 
217
- Every UI operation is an API operation. `fz ui routes --json` discovers the exact actions allowed by the signed-in account’s current realm, lifecycle stage and grants from the API’s enforced matrix, so the CLI and AI agents carry no copied route list. `fz api` (also `fz ui`) executes authenticated JSON GET, POST, PUT, PATCH and DELETE without requiring a browser visit for ordinary work. Paths are forced onto the signed-in origin, query values are repeatable, bodies can be inline, stdin or a file, and server grants plus fresh-proof rules remain authoritative. `fz project init|sync|check` separately gives AI tools one Git-persisted project memory.
217
+ Every UI operation is an API operation. `fz ui routes --json` discovers the exact actions allowed by the signed-in account’s current workspace, lifecycle stage and permissions from the API’s enforced matrix, so the CLI and AI agents carry no copied route list. `fz api` (also `fz ui`) executes authenticated JSON GET, POST, PUT, PATCH and DELETE without requiring a browser visit for ordinary work. Paths are forced onto the signed-in origin, query values are repeatable, bodies can be inline, stdin or a file, and server permissions plus fresh-proof rules remain authoritative. `fz project init|sync|check` separately gives AI tools one Git-persisted project memory.
218
218
 
219
219
  ```text
220
220
  fz ui routes --json
@@ -917,7 +917,7 @@ async function postSignedNode(options, path, body) {
917
917
  }
918
918
 
919
919
  // src/version.ts
920
- var VERSION3 = "0.1.128";
920
+ var VERSION3 = "0.1.129";
921
921
 
922
922
  // src/agent-heartbeat.ts
923
923
  function readAgentHostMetrics() {
@@ -210,6 +210,7 @@ export declare function interruptedPlatformReleaseIdentityDigests(config: Platfo
210
210
  export declare function bootstrapStatus(host?: BootstrapHost): Promise<BootstrapStatus>;
211
211
  export declare function applyBootstrap(input: BootstrapConfig, host?: BootstrapHost, secrets?: BootstrapSecrets, dependencies?: {
212
212
  fetcher?: typeof fetch;
213
+ reuseBoundPlatformCredentials?: boolean;
213
214
  }): Promise<BootstrapResult>;
214
215
  export declare function readBootstrapConfig(path: string): BootstrapConfig;
215
216
  interface BootstrapAgentInitialBundle {
package/dist/bootstrap.js CHANGED
@@ -1460,7 +1460,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1460
1460
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1461
1461
 
1462
1462
  // src/version.ts
1463
- var VERSION = "0.1.128";
1463
+ var VERSION = "0.1.129";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -4757,7 +4757,11 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4757
4757
  }
4758
4758
  allowInterruptedReleaseRebind = !exactIdentity && (boundedReleaseResume || approvedIdentityMigration);
4759
4759
  }
4760
- const platformPrivate = config.kind === "platform" ? (() => {
4760
+ const reuseBoundPlatformCredentials = config.kind === "platform" && dependencies.reuseBoundPlatformCredentials === true;
4761
+ if (reuseBoundPlatformCredentials && (!installed || !host.exists("/var/lib/forgezero/enrolment.json"))) {
4762
+ throw new Error("stored-credential promotion requires an installed enrolled platform compute");
4763
+ }
4764
+ const platformPrivate = config.kind === "platform" && !reuseBoundPlatformCredentials ? (() => {
4761
4765
  if (!secrets)
4762
4766
  throw new Error("platform apply requires attended credentials on stdin");
4763
4767
  const checked4 = validatePlatformBootstrapSecrets(config, secrets, {
@@ -4800,7 +4804,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4800
4804
  if (cloudflarePrivate && !host.exists(CF_API_CREDENTIAL)) {
4801
4805
  await seal(host, "CF_API_TOKEN", CF_API_CREDENTIAL, cloudflarePrivate.cloudflareApiToken);
4802
4806
  }
4803
- if (config.kind === "platform" && platformPrivate.cloudflareTunnelToken && !host.exists(CF_TUNNEL_API_CREDENTIAL)) {
4807
+ if (config.kind === "platform" && platformPrivate?.cloudflareTunnelToken && !host.exists(CF_TUNNEL_API_CREDENTIAL)) {
4804
4808
  await seal(host, "CF_TUNNEL_TOKEN", CF_TUNNEL_API_CREDENTIAL, platformPrivate.cloudflareTunnelToken);
4805
4809
  }
4806
4810
  const realtimeSecrets = cloudflare?.realtime && cloudflarePrivate ? deriveCloudflareRealtimeSecrets(cloudflarePrivate.cloudflareApiToken) : undefined;
@@ -4858,24 +4862,38 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4858
4862
  await verifyBootstrapBundleOnHost(host, config, true);
4859
4863
  }
4860
4864
  if (config.kind === "platform") {
4861
- const root = platformPrivate.root;
4862
- if (!host.exists(JWT_CREDENTIAL)) {
4863
- await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
4864
- }
4865
- if (!host.exists(ARANGO_ROOT_CREDENTIAL)) {
4866
- await seal(host, "arangodb-root-password", ARANGO_ROOT_CREDENTIAL, `fzr_${derive(root, "forgezero/cluster/arangodb-root-password/v1")}`);
4867
- }
4868
- await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
4869
- await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
4870
4865
  const emailCredentialName = config.runtime.environment.email?.provider === "smtp" ? "fz_smtp.password" : config.runtime.environment.email?.provider === "jetemail" ? "fz_jetemail.apiKey" : undefined;
4871
- for (const [name, source] of Object.entries({
4872
- ...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
4873
- "backup.s3.secretAccessKey": platformPrivate.backup
4874
- })) {
4875
- if (source) {
4876
- const destination2 = `${CREDS}/${name}.cred`;
4877
- if (!host.exists(destination2)) {
4878
- await seal(host, name, destination2, source);
4866
+ if (reuseBoundPlatformCredentials) {
4867
+ const required = [
4868
+ ...config.database.role !== "none" ? [JWT_CREDENTIAL, ARANGO_ROOT_CREDENTIAL] : [],
4869
+ SEED_CREDENTIAL,
4870
+ BACKUP_RECOVERY_CREDENTIAL,
4871
+ ...emailCredentialName ? [`${CREDS}/${emailCredentialName}.cred`] : [],
4872
+ ...config.runtime.environment.backup ? [`${CREDS}/backup.s3.secretAccessKey.cred`] : [],
4873
+ ...installed?.cloudflare ? [CF_API_CREDENTIAL, CF_TUNNEL_API_CREDENTIAL, TUNNEL_CREDENTIAL] : [],
4874
+ ...installed?.realtime ? [REALTIME_PUBLISH_CREDENTIAL, REALTIME_TICKET_CREDENTIAL] : []
4875
+ ];
4876
+ const missing = required.find((path) => !host.exists(path));
4877
+ if (missing)
4878
+ throw new Error(`stored platform credential is missing: ${missing}`);
4879
+ } else {
4880
+ const root = platformPrivate.root;
4881
+ if (!host.exists(JWT_CREDENTIAL)) {
4882
+ await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
4883
+ }
4884
+ if (!host.exists(ARANGO_ROOT_CREDENTIAL)) {
4885
+ await seal(host, "arangodb-root-password", ARANGO_ROOT_CREDENTIAL, `fzr_${derive(root, "forgezero/cluster/arangodb-root-password/v1")}`);
4886
+ }
4887
+ await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
4888
+ await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
4889
+ for (const [name, source] of Object.entries({
4890
+ ...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
4891
+ "backup.s3.secretAccessKey": platformPrivate.backup
4892
+ })) {
4893
+ if (source) {
4894
+ const destination2 = `${CREDS}/${name}.cred`;
4895
+ if (!host.exists(destination2))
4896
+ await seal(host, name, destination2, source);
4879
4897
  }
4880
4898
  }
4881
4899
  }
@@ -5040,7 +5058,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
5040
5058
  host.remove(config.cloudflareHandoff.handoffFile);
5041
5059
  }
5042
5060
  let launch;
5043
- if (config.kind === "platform" && config.database.role === "master") {
5061
+ if (config.kind === "platform" && config.database.role === "master" && !reuseBoundPlatformCredentials) {
5044
5062
  const invitePath = `${config.runtime.environment.sharedDirectory}/platform-invite.token`;
5045
5063
  if (!host.exists(invitePath))
5046
5064
  throw new Error("platform invite is missing after deployment");
package/dist/fz-agent.js CHANGED
@@ -9640,9 +9640,47 @@ async function writeAndCloseProcessInput(input, value) {
9640
9640
  }
9641
9641
 
9642
9642
  // src/version.ts
9643
- var VERSION2 = "0.1.128";
9643
+ var VERSION2 = "0.1.129";
9644
9644
 
9645
9645
  // src/ssh-bootstrap.ts
9646
+ function deriveMetalAdmissionProvisioning(lscpu) {
9647
+ const rows = lscpu.split(`
9648
+ `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => {
9649
+ const fields = line.split(",");
9650
+ if (fields.length !== 4 || fields.some((field) => !/^\d+$/.test(field))) {
9651
+ throw new SshBootstrapError("METAL_CPU_TOPOLOGY_INVALID");
9652
+ }
9653
+ return { cpu: Number(fields[0]), core: `${fields[2]}:${fields[1]}` };
9654
+ });
9655
+ if (rows.length < 2 || new Set(rows.map(({ cpu }) => cpu)).size !== rows.length) {
9656
+ throw new SshBootstrapError("METAL_CPU_TOPOLOGY_INVALID");
9657
+ }
9658
+ const cores = new Map;
9659
+ for (const row2 of rows)
9660
+ cores.set(row2.core, [...cores.get(row2.core) ?? [], row2.cpu]);
9661
+ if (cores.size < 2)
9662
+ throw new SshBootstrapError("METAL_CPU_CAPACITY_INSUFFICIENT");
9663
+ const ordered = [...cores.values()].map((cpus2) => cpus2.sort((a, b) => a - b)).sort((left, right) => left[0] - right[0]);
9664
+ const threadsPerCore = ordered[0].length;
9665
+ if (threadsPerCore < 1 || threadsPerCore > 8 || ordered.some((cpus2) => cpus2.length !== threadsPerCore)) {
9666
+ throw new SshBootstrapError("METAL_CPU_TOPOLOGY_INVALID");
9667
+ }
9668
+ const linuxList = (values) => [...values].sort((a, b) => a - b).join(",");
9669
+ const housekeeping = ordered.shift();
9670
+ const guestCpus = Array.from({ length: threadsPerCore }, (_, thread) => ordered.map((cpus2) => cpus2[thread])).flat();
9671
+ return {
9672
+ volumeGroup: "fzvg",
9673
+ bridge: "fzbr0",
9674
+ subnetPrefix: "10.42.0",
9675
+ addressStart: 20,
9676
+ addressEnd: 200,
9677
+ gateway: "10.42.0.1",
9678
+ nameservers: ["1.1.1.1", "1.0.0.1"],
9679
+ cpuPools: [{ key: "host-capacity", cpus: linuxList(guestCpus), physicalCores: ordered.length }],
9680
+ housekeepingCpus: linuxList(housekeeping)
9681
+ };
9682
+ }
9683
+
9646
9684
  class SshBootstrapError extends Error {
9647
9685
  code;
9648
9686
  constructor(code, message = code) {
@@ -9677,7 +9715,7 @@ var validateClaim = (claim) => {
9677
9715
  if (!/^[A-Za-z0-9_-]{1,64}$/.test(claim.computeKey) || !/^[A-Za-z0-9_-]{16,128}$/.test(claim.claimToken) || !/^fze_[A-Za-z0-9_-]{32,128}$/.test(claim.enrolmentToken) || !/^[a-z0-9][a-z0-9_-]{0,62}$/.test(claim.realm) || !/^[A-Za-z0-9_-]{1,64}$/.test(claim.projectKey) || !/^[A-Za-z0-9_-]{1,64}$/.test(claim.environmentKey))
9678
9716
  throw new SshBootstrapError("CLAIM_INVALID");
9679
9717
  const { host, port, user, hostKeySha256, nodeHostname, operatingSystem } = claim.target;
9680
- if (!host || host.length > 253 || /[\s/@]/.test(host) || !Number.isSafeInteger(port) || port < 1 || port > 65535 || !/^[a-z_][a-z0-9_-]{0,31}$/.test(user) || !/^SHA256:[A-Za-z0-9+/]{43}$/.test(hostKeySha256) || !/^[a-z0-9](?:[a-z0-9.-]{1,251}[a-z0-9])$/.test(nodeHostname) || !nodeHostname.includes(".")) {
9718
+ if (!host || host.length > 253 || /[\s/@]/.test(host) || !Number.isSafeInteger(port) || port < 1 || port > 65535 || !/^[a-z_][a-z0-9_-]{0,31}$/.test(user) || hostKeySha256 !== "" && !/^SHA256:[A-Za-z0-9+/]{43}$/.test(hostKeySha256) || !/^[a-z0-9](?:[a-z0-9.-]{1,251}[a-z0-9])$/.test(nodeHostname) || !nodeHostname.includes(".")) {
9681
9719
  throw new SshBootstrapError("CLAIM_INVALID");
9682
9720
  }
9683
9721
  if (operatingSystem?.id !== "ubuntu" || !["24.04", "26.04"].includes(operatingSystem.version) || operatingSystem.architecture !== "x64")
@@ -9738,11 +9776,14 @@ async function pinnedKnownHost(claim, pinned, directory, exec) {
9738
9776
  const candidates = scanned.split(`
9739
9777
  `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
9740
9778
  let matched;
9779
+ let observedFingerprint = "";
9741
9780
  for (const line of candidates) {
9742
- const fingerprint = await checked(exec, ["ssh-keygen", "-E", "sha256", "-lf", "-"], "HOST_KEY_INVALID", { stdin: `${line}
9781
+ const result = await checked(exec, ["ssh-keygen", "-E", "sha256", "-lf", "-"], "HOST_KEY_INVALID", { stdin: `${line}
9743
9782
  ` });
9744
- if (fingerprint.split(/\s+/).includes(claim.target.hostKeySha256)) {
9783
+ const fingerprint = result.split(/\s+/).find((part) => /^SHA256:[A-Za-z0-9+/]{43}$/.test(part)) ?? "";
9784
+ if (fingerprint && (!claim.target.hostKeySha256 || fingerprint === claim.target.hostKeySha256)) {
9745
9785
  matched = line;
9786
+ observedFingerprint = fingerprint;
9746
9787
  break;
9747
9788
  }
9748
9789
  }
@@ -9754,7 +9795,7 @@ async function pinnedKnownHost(claim, pinned, directory, exec) {
9754
9795
  const path2 = join3(directory, "known_hosts");
9755
9796
  writeFileSync5(path2, `${pinned.hostKeyAlias} ${key}
9756
9797
  `, { mode: 384, flag: "wx" });
9757
- return path2;
9798
+ return { path: path2, fingerprint: observedFingerprint };
9758
9799
  }
9759
9800
  var BUN_RELEASE_URL = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.14/bun-linux-x64.zip";
9760
9801
  var verifiedBunArchive = async (directory) => {
@@ -9809,7 +9850,9 @@ process.stdout.write(process.env.FZ_SSH_PASSWORD ?? "")
9809
9850
  if (!resolved || resolved.protocol !== "ssh")
9810
9851
  throw new SshBootstrapError("TARGET_ADDRESS_REFUSED");
9811
9852
  pinned = resolved;
9812
- const knownHosts = await pinnedKnownHost(claim, pinned, directory, exec);
9853
+ const observedHost = await pinnedKnownHost(claim, pinned, directory, exec);
9854
+ claim.target.hostKeySha256 = observedHost.fingerprint;
9855
+ const knownHosts = observedHost.path;
9813
9856
  const ssh = sshOptions(claim, pinned, knownHosts, sshKeyPath);
9814
9857
  remoteDirectory = await checked(exec, ["ssh", ...ssh, target(claim, pinned), "--", "mktemp", "-d", "/tmp/forgezero-bootstrap.XXXXXXXX"], "SSH_UNREACHABLE");
9815
9858
  if (!/^\/tmp\/forgezero-bootstrap\.[A-Za-z0-9]{8}$/.test(remoteDirectory))
@@ -10153,7 +10196,9 @@ process.stdout.write(process.env.FZ_SSH_PASSWORD ?? "")
10153
10196
  if (!resolved || resolved.protocol !== "ssh")
10154
10197
  throw new SshBootstrapError("TARGET_ADDRESS_REFUSED");
10155
10198
  pinned = resolved;
10156
- const knownHosts = await pinnedKnownHost(claim, pinned, directory, exec);
10199
+ const observedHost = await pinnedKnownHost(claim, pinned, directory, exec);
10200
+ claim.target.hostKeySha256 = observedHost.fingerprint;
10201
+ const knownHosts = observedHost.path;
10157
10202
  const ssh = sshOptions(claim, pinned, knownHosts, sshKeyPath);
10158
10203
  const destination = target(claim, pinned);
10159
10204
  remoteDirectory = await checked(exec, ["ssh", ...ssh, destination, "--", "mktemp", "-d", "/tmp/forgezero-metal-admission.XXXXXXXX"], "SSH_UNREACHABLE");
@@ -10173,6 +10218,13 @@ process.stdout.write(process.env.FZ_SSH_PASSWORD ?? "")
10173
10218
  writeFileSync5(authorizedPath, authorized, { mode: 384, flag: "wx" });
10174
10219
  await checked(exec, ["scp", ...scpOptions(claim, pinned, knownHosts, sshKeyPath), authorizedPath, `${destination}:${remoteDirectory}/authorized_keys`], "SSH_IDENTITY_INSTALL_FAILED");
10175
10220
  await remote(exec, ssh, destination, "SSH_IDENTITY_INSTALL_FAILED", ["/usr/bin/install", "-m", "0600", `${remoteDirectory}/authorized_keys`, `${home}/.ssh/authorized_keys`]);
10221
+ await remote(exec, ssh, destination, "METAL_STORAGE_UNAVAILABLE", ["/usr/bin/sudo", "-n", "/usr/sbin/vgs", "fzvg"]);
10222
+ await remote(exec, ssh, destination, "METAL_NETWORK_UNAVAILABLE", ["/usr/bin/sudo", "-n", "/usr/sbin/ip", "link", "show", "fzbr0"]);
10223
+ const topology = await remote(exec, ssh, destination, "METAL_CPU_TOPOLOGY_INVALID", [
10224
+ "/usr/bin/lscpu",
10225
+ "--parse=CPU,CORE,SOCKET,NODE"
10226
+ ]);
10227
+ const provisioning = deriveMetalAdmissionProvisioning(topology);
10176
10228
  const fzCliPath = options.fzCliPath ?? fileURLToPath(new URL("./fz.js", import.meta.url));
10177
10229
  const fzAgentPath = options.fzAgentPath ?? fileURLToPath(new URL("./fz-agent.js", import.meta.url));
10178
10230
  for (const [source, name] of [[fzCliPath, "fz.js"], [fzAgentPath, "fz-agent.js"]]) {
@@ -10191,7 +10243,7 @@ process.stdout.write(process.env.FZ_SSH_PASSWORD ?? "")
10191
10243
  hostTelemetryEndpoint: "http://127.0.0.1:4318",
10192
10244
  hostTelemetryUnit: "forgezero-otel-collector.service",
10193
10245
  profile: {
10194
- ...claim.provisioning,
10246
+ ...provisioning,
10195
10247
  stateDir: "/etc/forgezero/metal-guests",
10196
10248
  seedDir: "/var/lib/forgezero/seed",
10197
10249
  unitDir: "/etc/systemd/system",
package/dist/fz.js CHANGED
@@ -5788,7 +5788,7 @@ var VaultRuntimeClientError, json = (value) => ({ method: "POST", body: JSON.str
5788
5788
  } catch {
5789
5789
  return;
5790
5790
  }
5791
- }, VERSION = "0.1.25";
5791
+ }, VERSION = "0.1.26";
5792
5792
  var init_dist = __esm(() => {
5793
5793
  init_vault_runtime();
5794
5794
  init_identity();
@@ -6007,7 +6007,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
6007
6007
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
6008
6008
 
6009
6009
  // src/version.ts
6010
- var VERSION2 = "0.1.128";
6010
+ var VERSION2 = "0.1.129";
6011
6011
 
6012
6012
  // src/software.ts
6013
6013
  var PINNED_BUN_VERSION = "1.3.14";
@@ -15377,7 +15377,11 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
15377
15377
  }
15378
15378
  allowInterruptedReleaseRebind = !exactIdentity && (boundedReleaseResume || approvedIdentityMigration);
15379
15379
  }
15380
- const platformPrivate = config.kind === "platform" ? (() => {
15380
+ const reuseBoundPlatformCredentials = config.kind === "platform" && dependencies.reuseBoundPlatformCredentials === true;
15381
+ if (reuseBoundPlatformCredentials && (!installed || !host.exists("/var/lib/forgezero/enrolment.json"))) {
15382
+ throw new Error("stored-credential promotion requires an installed enrolled platform compute");
15383
+ }
15384
+ const platformPrivate = config.kind === "platform" && !reuseBoundPlatformCredentials ? (() => {
15381
15385
  if (!secrets)
15382
15386
  throw new Error("platform apply requires attended credentials on stdin");
15383
15387
  const checked4 = validatePlatformBootstrapSecrets(config, secrets, {
@@ -15420,7 +15424,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
15420
15424
  if (cloudflarePrivate && !host.exists(CF_API_CREDENTIAL)) {
15421
15425
  await seal(host, "CF_API_TOKEN", CF_API_CREDENTIAL, cloudflarePrivate.cloudflareApiToken);
15422
15426
  }
15423
- if (config.kind === "platform" && platformPrivate.cloudflareTunnelToken && !host.exists(CF_TUNNEL_API_CREDENTIAL)) {
15427
+ if (config.kind === "platform" && platformPrivate?.cloudflareTunnelToken && !host.exists(CF_TUNNEL_API_CREDENTIAL)) {
15424
15428
  await seal(host, "CF_TUNNEL_TOKEN", CF_TUNNEL_API_CREDENTIAL, platformPrivate.cloudflareTunnelToken);
15425
15429
  }
15426
15430
  const realtimeSecrets = cloudflare?.realtime && cloudflarePrivate ? deriveCloudflareRealtimeSecrets(cloudflarePrivate.cloudflareApiToken) : undefined;
@@ -15478,24 +15482,38 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
15478
15482
  await verifyBootstrapBundleOnHost(host, config, true);
15479
15483
  }
15480
15484
  if (config.kind === "platform") {
15481
- const root = platformPrivate.root;
15482
- if (!host.exists(JWT_CREDENTIAL)) {
15483
- await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
15484
- }
15485
- if (!host.exists(ARANGO_ROOT_CREDENTIAL)) {
15486
- await seal(host, "arangodb-root-password", ARANGO_ROOT_CREDENTIAL, `fzr_${derive(root, "forgezero/cluster/arangodb-root-password/v1")}`);
15487
- }
15488
- await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
15489
- await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
15490
15485
  const emailCredentialName = config.runtime.environment.email?.provider === "smtp" ? "fz_smtp.password" : config.runtime.environment.email?.provider === "jetemail" ? "fz_jetemail.apiKey" : undefined;
15491
- for (const [name, source] of Object.entries({
15492
- ...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
15493
- "backup.s3.secretAccessKey": platformPrivate.backup
15494
- })) {
15495
- if (source) {
15496
- const destination2 = `${CREDS}/${name}.cred`;
15497
- if (!host.exists(destination2)) {
15498
- await seal(host, name, destination2, source);
15486
+ if (reuseBoundPlatformCredentials) {
15487
+ const required = [
15488
+ ...config.database.role !== "none" ? [JWT_CREDENTIAL, ARANGO_ROOT_CREDENTIAL] : [],
15489
+ SEED_CREDENTIAL,
15490
+ BACKUP_RECOVERY_CREDENTIAL,
15491
+ ...emailCredentialName ? [`${CREDS}/${emailCredentialName}.cred`] : [],
15492
+ ...config.runtime.environment.backup ? [`${CREDS}/backup.s3.secretAccessKey.cred`] : [],
15493
+ ...installed?.cloudflare ? [CF_API_CREDENTIAL, CF_TUNNEL_API_CREDENTIAL, TUNNEL_CREDENTIAL] : [],
15494
+ ...installed?.realtime ? [REALTIME_PUBLISH_CREDENTIAL, REALTIME_TICKET_CREDENTIAL] : []
15495
+ ];
15496
+ const missing = required.find((path) => !host.exists(path));
15497
+ if (missing)
15498
+ throw new Error(`stored platform credential is missing: ${missing}`);
15499
+ } else {
15500
+ const root = platformPrivate.root;
15501
+ if (!host.exists(JWT_CREDENTIAL)) {
15502
+ await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
15503
+ }
15504
+ if (!host.exists(ARANGO_ROOT_CREDENTIAL)) {
15505
+ await seal(host, "arangodb-root-password", ARANGO_ROOT_CREDENTIAL, `fzr_${derive(root, "forgezero/cluster/arangodb-root-password/v1")}`);
15506
+ }
15507
+ await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
15508
+ await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
15509
+ for (const [name, source] of Object.entries({
15510
+ ...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
15511
+ "backup.s3.secretAccessKey": platformPrivate.backup
15512
+ })) {
15513
+ if (source) {
15514
+ const destination2 = `${CREDS}/${name}.cred`;
15515
+ if (!host.exists(destination2))
15516
+ await seal(host, name, destination2, source);
15499
15517
  }
15500
15518
  }
15501
15519
  }
@@ -15660,7 +15678,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
15660
15678
  host.remove(config.cloudflareHandoff.handoffFile);
15661
15679
  }
15662
15680
  let launch;
15663
- if (config.kind === "platform" && config.database.role === "master") {
15681
+ if (config.kind === "platform" && config.database.role === "master" && !reuseBoundPlatformCredentials) {
15664
15682
  const invitePath = `${config.runtime.environment.sharedDirectory}/platform-invite.token`;
15665
15683
  if (!host.exists(invitePath))
15666
15684
  throw new Error("platform invite is missing after deployment");
@@ -17621,7 +17639,14 @@ function planOperatorPlatformBootstrap(request, mode) {
17621
17639
  user: request.target.user,
17622
17640
  ...request.target.jump ? { via: request.target.jump.address } : {}
17623
17641
  },
17624
- steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : [
17642
+ steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : mode === "promote" ? [
17643
+ "verify pinned SSH transport and caller-approved SSH agent",
17644
+ "copy pinned Bun and packaged fz artifacts to a private staging directory",
17645
+ "verify and copy the one immutable API Git bundle plus its manifest",
17646
+ "stage the rewritten owner-only platform config without plaintext credentials",
17647
+ "promote the exact release using the credentials already sealed on the enrolled compute",
17648
+ "remove transient local and remote staging data"
17649
+ ] : [
17625
17650
  "verify pinned SSH transport and caller-approved SSH agent",
17626
17651
  "copy pinned Bun and packaged fz artifacts to a private staging directory",
17627
17652
  "verify and copy the one immutable API Git bundle plus its manifest",
@@ -18124,7 +18149,7 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
18124
18149
  allowStoredCloudflareCredentials: mode === "repair"
18125
18150
  }) : undefined;
18126
18151
  const staged = await stageConfig(config, directory, {
18127
- reuseBoundCloudflare: mode === "repair"
18152
+ reuseBoundCloudflare: mode === "repair" || mode === "promote"
18128
18153
  });
18129
18154
  await installPackagedAgent(request, knownHosts, exec, directory, remoteTemp, options);
18130
18155
  await copy(exec, request, knownHosts, staged.path, `${remoteTemp}/platform-config.json`, true);
@@ -18141,8 +18166,8 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
18141
18166
  "-n",
18142
18167
  "/usr/local/bin/fz",
18143
18168
  "bootstrap",
18144
- mode === "repair" ? "repair" : "platform",
18145
- "credentials-stdin"
18169
+ mode === "apply" ? "platform" : "repair",
18170
+ mode === "promote" ? "stored-credentials" : "credentials-stdin"
18146
18171
  ];
18147
18172
  command.push("--bootstrap-config", `${REMOTE_STAGE}/platform-config.json`, "--apply");
18148
18173
  const output = await remote(exec, request, knownHosts, command, "remote typed bootstrap", mode === "apply", secrets ? `${JSON.stringify(secrets)}
@@ -20067,6 +20092,31 @@ async function prepareForgeZeroPlatformLaunch(environment, outputDirectory, proj
20067
20092
  owner: owner ?? forgeZeroLaunchOwnerInput()
20068
20093
  });
20069
20094
  }
20095
+ var GENESIS_HOST_KEY_ATTEMPTS = 12;
20096
+ var GENESIS_HOST_KEY_RETRY_MS = 2000;
20097
+ async function reconcileForgeZeroGenesisGuests(outputDirectory) {
20098
+ const base = dirname14(outputDirectory);
20099
+ const metalRequestFile = join13(base, "metal", "metal-remote.json");
20100
+ const guestHostKeysFile = join13(base, "genesis-guest-host-keys.json");
20101
+ const metalRequest = readOperatorMetalBootstrapRequest(metalRequestFile, { validateMetalConfig: false });
20102
+ await applyOperatorMetalBootstrap(metalRequest, "genesis");
20103
+ let lastFailure;
20104
+ for (let attempt = 1;attempt <= GENESIS_HOST_KEY_ATTEMPTS; attempt += 1) {
20105
+ try {
20106
+ const result = await applyOperatorMetalBootstrap(metalRequest, "host-keys");
20107
+ const evidence = JSON.parse(result.output);
20108
+ writeDerivedLaunchFile(guestHostKeysFile, (temporary) => writeOperatorGuestHostKeyEvidence(temporary, evidence, metalRequest));
20109
+ readOperatorGuestHostKeyEvidence(guestHostKeysFile, metalRequest);
20110
+ return;
20111
+ } catch (cause) {
20112
+ lastFailure = cause;
20113
+ if (attempt < GENESIS_HOST_KEY_ATTEMPTS) {
20114
+ await new Promise((resolve14) => setTimeout(resolve14, GENESIS_HOST_KEY_RETRY_MS));
20115
+ }
20116
+ }
20117
+ }
20118
+ throw new Error(`reviewed genesis guests did not become SSH-ready: ${lastFailure instanceof Error ? lastFailure.message : String(lastFailure)}`);
20119
+ }
20070
20120
  function interactiveBootstrap(kind, genesisGuests, genesisCloudflareCheckpoint) {
20071
20121
  if (!process.stdin.isTTY)
20072
20122
  throw new Error("non-interactive bootstrap requires --bootstrap-config <private-json-file>");
@@ -20266,6 +20316,8 @@ async function cmdBootstrap(options, args) {
20266
20316
  }
20267
20317
  const outputDirectory = options.outputPath ?? join13(homedir2(), ".forgezero-secure", environment, "genesis");
20268
20318
  const environmentInput = options.bootstrapSecretsEnvFile ? readPlatformLaunchEnvironment(options.bootstrapSecretsEnvFile) : undefined;
20319
+ if (options.apply)
20320
+ await reconcileForgeZeroGenesisGuests(outputDirectory);
20269
20321
  const fleet = readOperatorPlatformBootstrapFleetRequest((await prepareForgeZeroPlatformLaunch(environment, outputDirectory, options.projectRoot, environmentInput?.owner)).fleetRequest);
20270
20322
  if (planOperatorPlatformFleetBootstrap(fleet, "apply").environment !== environment) {
20271
20323
  throw new Error("existing platform fleet does not match the selected launch profile");
@@ -20277,8 +20329,8 @@ async function cmdBootstrap(options, args) {
20277
20329
  }
20278
20330
  if (operation === "platform" && args[1] === "fleet") {
20279
20331
  const mode = args[2];
20280
- if (!mode || !["apply", "status"].includes(mode) || args[3] !== undefined) {
20281
- throw new Error("Usage: fz bootstrap platform fleet <apply|status> --bootstrap-config <owner-only-platform-fleet-remote.json> [--apply]");
20332
+ if (!mode || !["apply", "promote", "status"].includes(mode) || args[3] !== undefined) {
20333
+ throw new Error("Usage: fz bootstrap platform fleet <apply|promote|status> --bootstrap-config <owner-only-platform-fleet-remote.json> [--apply]");
20282
20334
  }
20283
20335
  if (!options.bootstrapConfigPath)
20284
20336
  throw new Error("remote platform fleet bootstrap requires --bootstrap-config");
@@ -20288,12 +20340,23 @@ async function cmdBootstrap(options, args) {
20288
20340
  out.line(JSON.stringify(result2, null, 2));
20289
20341
  return result2.ok ? 0 : 1;
20290
20342
  }
20343
+ if (mode === "promote") {
20344
+ const plan2 = planOperatorPlatformFleetBootstrap(fleet, mode);
20345
+ out.line(JSON.stringify(plan2, null, 2));
20346
+ if (!options.apply) {
20347
+ out.step("Review the pinned secret-free promotion, then repeat with --apply.");
20348
+ return 0;
20349
+ }
20350
+ const result2 = await applyOperatorPlatformFleetBootstrap(fleet, mode);
20351
+ out.line(JSON.stringify(result2, null, 2));
20352
+ return result2.ok ? 0 : 1;
20353
+ }
20291
20354
  return await runAttendedPlatformFleet(fleet, options.apply);
20292
20355
  }
20293
20356
  if (operation === "platform" && args[1] === "remote") {
20294
20357
  const mode = args[2];
20295
- if (!mode || !["apply", "repair", "status"].includes(mode) || args[3] !== undefined) {
20296
- throw new Error("Usage: fz bootstrap platform remote <apply|repair|status> --bootstrap-config <owner-only-request.json> [--apply]");
20358
+ if (!mode || !["apply", "repair", "promote", "status"].includes(mode) || args[3] !== undefined) {
20359
+ throw new Error("Usage: fz bootstrap platform remote <apply|repair|promote|status> --bootstrap-config <owner-only-request.json> [--apply]");
20297
20360
  }
20298
20361
  if (!options.bootstrapConfigPath)
20299
20362
  throw new Error("remote platform bootstrap requires --bootstrap-config");
@@ -20406,8 +20469,9 @@ async function cmdBootstrap(options, args) {
20406
20469
  return 0;
20407
20470
  }
20408
20471
  const credentialStdin = (operation === "platform" || operation === "repair") && args[1] === "credentials-stdin";
20409
- if (credentialStdin && args[2] !== undefined)
20410
- throw new Error("platform credential stdin accepts no additional positional arguments");
20472
+ const storedCredentials = operation === "repair" && args[1] === "stored-credentials";
20473
+ if ((credentialStdin || storedCredentials) && args[2] !== undefined)
20474
+ throw new Error("platform credential mode accepts no additional positional arguments");
20411
20475
  const installedBootstrapKind = () => resolveInstalledBootstrapKind({
20412
20476
  metal: existsSync13(METAL_BOOTSTRAP_STATE_PATH),
20413
20477
  compute: existsSync13(BOOTSTRAP_STATE_PATH)
@@ -20451,7 +20515,7 @@ async function cmdBootstrap(options, args) {
20451
20515
  return 0;
20452
20516
  }
20453
20517
  if (!["platform", "repair"].includes(operation)) {
20454
- throw new Error("Usage: fz bootstrap config <metal|platform|cloudflare>|platform [launch|bundle|fleet <apply|status>|remote <apply|status>|cloudflare [verify|finalize]]|metal [remote <apply|genesis|genesis-cleanup|rehearsal|rehearsal-cleanup|host-keys|rehearsal-host-keys|status>]|status|repair [--bootstrap-config <path>] [--apply]");
20518
+ throw new Error("Usage: fz bootstrap config <metal|platform|cloudflare>|platform [launch|bundle|fleet <apply|promote|status>|remote <apply|repair|promote|status>|cloudflare [verify|finalize]]|metal [remote <apply|genesis|genesis-cleanup|rehearsal|rehearsal-cleanup|host-keys|rehearsal-host-keys|status>]|status|repair [--bootstrap-config <path>] [--apply]");
20455
20519
  }
20456
20520
  const config = options.bootstrapConfigPath ? readBootstrapConfig(options.bootstrapConfigPath) : operation === "repair" ? (() => {
20457
20521
  throw new Error("repair requires --bootstrap-config so immutable coordinates are revalidated");
@@ -20468,7 +20532,7 @@ async function cmdBootstrap(options, args) {
20468
20532
  out.step("Review the plan, then repeat with --apply as root.");
20469
20533
  return 0;
20470
20534
  }
20471
- const secrets = config.kind === "platform" && !credentialStdin ? await promptPlatformBootstrapSecrets(config, undefined, {
20535
+ const secrets = config.kind === "platform" && !credentialStdin && !storedCredentials ? await promptPlatformBootstrapSecrets(config, undefined, {
20472
20536
  reuseBoundCloudflare: operation === "repair"
20473
20537
  }) : undefined;
20474
20538
  let stdinSecrets = secrets;
@@ -20486,7 +20550,9 @@ async function cmdBootstrap(options, args) {
20486
20550
  allowStoredCloudflareCredentials: operation === "repair"
20487
20551
  });
20488
20552
  }
20489
- const result = await applyBootstrap(config, undefined, stdinSecrets);
20553
+ const result = await applyBootstrap(config, undefined, stdinSecrets, {
20554
+ reuseBoundPlatformCredentials: storedCredentials
20555
+ });
20490
20556
  out.line(JSON.stringify(result, null, 2));
20491
20557
  return 0;
20492
20558
  } catch (cause) {
@@ -21114,10 +21180,10 @@ function usage() {
21114
21180
  One profile-driven attended launch. Public topology,
21115
21181
  bundle and edge names are derived; the owner supplies
21116
21182
  only email settings, hidden provider tokens and APPLY
21117
- fz bootstrap platform remote <apply|repair|status>
21183
+ fz bootstrap platform remote <apply|repair|promote|status>
21118
21184
  Run typed bootstrap from the operator laptop through
21119
- a pinned host; apply copies and verifies that bundle
21120
- fz bootstrap platform fleet <apply|status>
21185
+ a pinned host; promote reuses sealed host credentials
21186
+ fz bootstrap platform fleet <apply|promote|status>
21121
21187
  One attended genesis operation: reconcile public
21122
21188
  hostnames/Tunnels, seal credentials, deploy the bundle,
21123
21189
  run all three nodes, verify ingress and print the invite
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
366
366
  }
367
367
 
368
368
  // src/version.ts
369
- var VERSION = "0.1.128";
369
+ var VERSION = "0.1.129";
370
370
 
371
371
  // src/otel-collector.ts
372
372
  var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
@@ -89,7 +89,7 @@ export interface OperatorPlatformFleetCoordinates {
89
89
  databaseRole: 'master' | 'joiner';
90
90
  }[];
91
91
  }
92
- export type OperatorPlatformBootstrapMode = 'apply' | 'repair' | 'status';
92
+ export type OperatorPlatformBootstrapMode = 'apply' | 'repair' | 'promote' | 'status';
93
93
  export type OperatorMetalBootstrapMode = 'apply' | 'genesis' | 'genesis-cleanup' | 'rehearsal' | 'rehearsal-cleanup' | 'host-keys' | 'rehearsal-host-keys' | 'status';
94
94
  export interface OperatorPlatformBootstrapPlan {
95
95
  kind: 'platform-remote';
@@ -1460,7 +1460,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1460
1460
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1461
1461
 
1462
1462
  // src/version.ts
1463
- var VERSION = "0.1.128";
1463
+ var VERSION = "0.1.129";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -4757,7 +4757,11 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4757
4757
  }
4758
4758
  allowInterruptedReleaseRebind = !exactIdentity && (boundedReleaseResume || approvedIdentityMigration);
4759
4759
  }
4760
- const platformPrivate = config.kind === "platform" ? (() => {
4760
+ const reuseBoundPlatformCredentials = config.kind === "platform" && dependencies.reuseBoundPlatformCredentials === true;
4761
+ if (reuseBoundPlatformCredentials && (!installed || !host.exists("/var/lib/forgezero/enrolment.json"))) {
4762
+ throw new Error("stored-credential promotion requires an installed enrolled platform compute");
4763
+ }
4764
+ const platformPrivate = config.kind === "platform" && !reuseBoundPlatformCredentials ? (() => {
4761
4765
  if (!secrets)
4762
4766
  throw new Error("platform apply requires attended credentials on stdin");
4763
4767
  const checked4 = validatePlatformBootstrapSecrets(config, secrets, {
@@ -4800,7 +4804,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4800
4804
  if (cloudflarePrivate && !host.exists(CF_API_CREDENTIAL)) {
4801
4805
  await seal(host, "CF_API_TOKEN", CF_API_CREDENTIAL, cloudflarePrivate.cloudflareApiToken);
4802
4806
  }
4803
- if (config.kind === "platform" && platformPrivate.cloudflareTunnelToken && !host.exists(CF_TUNNEL_API_CREDENTIAL)) {
4807
+ if (config.kind === "platform" && platformPrivate?.cloudflareTunnelToken && !host.exists(CF_TUNNEL_API_CREDENTIAL)) {
4804
4808
  await seal(host, "CF_TUNNEL_TOKEN", CF_TUNNEL_API_CREDENTIAL, platformPrivate.cloudflareTunnelToken);
4805
4809
  }
4806
4810
  const realtimeSecrets = cloudflare?.realtime && cloudflarePrivate ? deriveCloudflareRealtimeSecrets(cloudflarePrivate.cloudflareApiToken) : undefined;
@@ -4858,24 +4862,38 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4858
4862
  await verifyBootstrapBundleOnHost(host, config, true);
4859
4863
  }
4860
4864
  if (config.kind === "platform") {
4861
- const root = platformPrivate.root;
4862
- if (!host.exists(JWT_CREDENTIAL)) {
4863
- await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
4864
- }
4865
- if (!host.exists(ARANGO_ROOT_CREDENTIAL)) {
4866
- await seal(host, "arangodb-root-password", ARANGO_ROOT_CREDENTIAL, `fzr_${derive(root, "forgezero/cluster/arangodb-root-password/v1")}`);
4867
- }
4868
- await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
4869
- await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
4870
4865
  const emailCredentialName = config.runtime.environment.email?.provider === "smtp" ? "fz_smtp.password" : config.runtime.environment.email?.provider === "jetemail" ? "fz_jetemail.apiKey" : undefined;
4871
- for (const [name, source] of Object.entries({
4872
- ...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
4873
- "backup.s3.secretAccessKey": platformPrivate.backup
4874
- })) {
4875
- if (source) {
4876
- const destination2 = `${CREDS}/${name}.cred`;
4877
- if (!host.exists(destination2)) {
4878
- await seal(host, name, destination2, source);
4866
+ if (reuseBoundPlatformCredentials) {
4867
+ const required = [
4868
+ ...config.database.role !== "none" ? [JWT_CREDENTIAL, ARANGO_ROOT_CREDENTIAL] : [],
4869
+ SEED_CREDENTIAL,
4870
+ BACKUP_RECOVERY_CREDENTIAL,
4871
+ ...emailCredentialName ? [`${CREDS}/${emailCredentialName}.cred`] : [],
4872
+ ...config.runtime.environment.backup ? [`${CREDS}/backup.s3.secretAccessKey.cred`] : [],
4873
+ ...installed?.cloudflare ? [CF_API_CREDENTIAL, CF_TUNNEL_API_CREDENTIAL, TUNNEL_CREDENTIAL] : [],
4874
+ ...installed?.realtime ? [REALTIME_PUBLISH_CREDENTIAL, REALTIME_TICKET_CREDENTIAL] : []
4875
+ ];
4876
+ const missing = required.find((path) => !host.exists(path));
4877
+ if (missing)
4878
+ throw new Error(`stored platform credential is missing: ${missing}`);
4879
+ } else {
4880
+ const root = platformPrivate.root;
4881
+ if (!host.exists(JWT_CREDENTIAL)) {
4882
+ await seal(host, "arangodb-jwt", JWT_CREDENTIAL, derive(root, "forgezero/cluster/arangodb-jwt/v1"));
4883
+ }
4884
+ if (!host.exists(ARANGO_ROOT_CREDENTIAL)) {
4885
+ await seal(host, "arangodb-root-password", ARANGO_ROOT_CREDENTIAL, `fzr_${derive(root, "forgezero/cluster/arangodb-root-password/v1")}`);
4886
+ }
4887
+ await seal(host, "seed-sync-root", SEED_CREDENTIAL, derive(root, "forgezero/cluster/seed-mesh/v1"));
4888
+ await seal(host, "backup-recovery-root", BACKUP_RECOVERY_CREDENTIAL, derive(root, "forgezero/backup/recovery-root/v1"));
4889
+ for (const [name, source] of Object.entries({
4890
+ ...emailCredentialName ? { [emailCredentialName]: platformPrivate.email } : {},
4891
+ "backup.s3.secretAccessKey": platformPrivate.backup
4892
+ })) {
4893
+ if (source) {
4894
+ const destination2 = `${CREDS}/${name}.cred`;
4895
+ if (!host.exists(destination2))
4896
+ await seal(host, name, destination2, source);
4879
4897
  }
4880
4898
  }
4881
4899
  }
@@ -5040,7 +5058,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
5040
5058
  host.remove(config.cloudflareHandoff.handoffFile);
5041
5059
  }
5042
5060
  let launch;
5043
- if (config.kind === "platform" && config.database.role === "master") {
5061
+ if (config.kind === "platform" && config.database.role === "master" && !reuseBoundPlatformCredentials) {
5044
5062
  const invitePath = `${config.runtime.environment.sharedDirectory}/platform-invite.token`;
5045
5063
  if (!host.exists(invitePath))
5046
5064
  throw new Error("platform invite is missing after deployment");
@@ -6737,7 +6755,14 @@ function planOperatorPlatformBootstrap(request, mode) {
6737
6755
  user: request.target.user,
6738
6756
  ...request.target.jump ? { via: request.target.jump.address } : {}
6739
6757
  },
6740
- steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : [
6758
+ steps: mode === "status" ? ["verify pinned SSH transport", "run typed fz bootstrap status"] : mode === "promote" ? [
6759
+ "verify pinned SSH transport and caller-approved SSH agent",
6760
+ "copy pinned Bun and packaged fz artifacts to a private staging directory",
6761
+ "verify and copy the one immutable API Git bundle plus its manifest",
6762
+ "stage the rewritten owner-only platform config without plaintext credentials",
6763
+ "promote the exact release using the credentials already sealed on the enrolled compute",
6764
+ "remove transient local and remote staging data"
6765
+ ] : [
6741
6766
  "verify pinned SSH transport and caller-approved SSH agent",
6742
6767
  "copy pinned Bun and packaged fz artifacts to a private staging directory",
6743
6768
  "verify and copy the one immutable API Git bundle plus its manifest",
@@ -7303,7 +7328,7 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
7303
7328
  allowStoredCloudflareCredentials: mode === "repair"
7304
7329
  }) : undefined;
7305
7330
  const staged = await stageConfig(config, directory, {
7306
- reuseBoundCloudflare: mode === "repair"
7331
+ reuseBoundCloudflare: mode === "repair" || mode === "promote"
7307
7332
  });
7308
7333
  await installPackagedAgent(request, knownHosts, exec, directory, remoteTemp, options);
7309
7334
  await copy(exec, request, knownHosts, staged.path, `${remoteTemp}/platform-config.json`, true);
@@ -7320,8 +7345,8 @@ async function applyOperatorPlatformBootstrap(request, mode, options = {}) {
7320
7345
  "-n",
7321
7346
  "/usr/local/bin/fz",
7322
7347
  "bootstrap",
7323
- mode === "repair" ? "repair" : "platform",
7324
- "credentials-stdin"
7348
+ mode === "apply" ? "platform" : "repair",
7349
+ mode === "promote" ? "stored-credentials" : "credentials-stdin"
7325
7350
  ];
7326
7351
  command.push("--bootstrap-config", `${REMOTE_STAGE}/platform-config.json`, "--apply");
7327
7352
  const output = await remote(exec, request, knownHosts, command, "remote typed bootstrap", mode === "apply", secrets ? `${JSON.stringify(secrets)}
@@ -3020,7 +3020,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3020
3020
  }
3021
3021
 
3022
3022
  // src/version.ts
3023
- var VERSION3 = "0.1.128";
3023
+ var VERSION3 = "0.1.129";
3024
3024
 
3025
3025
  // src/egress-policy.ts
3026
3026
  import { realpathSync as realpathSync3 } from "node:fs";
package/dist/provision.js CHANGED
@@ -3020,7 +3020,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3020
3020
  }
3021
3021
 
3022
3022
  // src/version.ts
3023
- var VERSION3 = "0.1.128";
3023
+ var VERSION3 = "0.1.129";
3024
3024
 
3025
3025
  // src/egress-policy.ts
3026
3026
  import { realpathSync as realpathSync3 } from "node:fs";
@@ -1,6 +1,7 @@
1
1
  import type { NodeKeyPair } from '@forgezero/runtime/identity';
2
2
  import type { AgentOperationTelemetry } from './telemetry-runtime';
3
3
  import { type GitHostResolver } from './git-egress';
4
+ import type { MetalBootstrapConfig } from './metal-bootstrap';
4
5
  export interface RemoteSshBootstrapClaim {
5
6
  computeKey: string;
6
7
  computeReference: string;
@@ -50,30 +51,16 @@ export interface RemoteMetalAdmissionClaim {
50
51
  runnerNodeKey: string;
51
52
  runnerSshPublicKey: string;
52
53
  challenge: string;
53
- provisioning: {
54
- volumeGroup: string;
55
- bridge: string;
56
- subnetPrefix: string;
57
- addressStart: number;
58
- addressEnd: number;
59
- gateway: string;
60
- nameservers: string[];
61
- cpuPools: Array<{
62
- key: string;
63
- cpus: string;
64
- physicalCores: number;
65
- memoryNodes?: string;
66
- }>;
67
- housekeepingCpus: string;
68
- housekeepingMemoryNodes?: string;
69
- confidential?: {
70
- cbitpos: number;
71
- reducedPhysBits: number;
72
- policy: string;
73
- };
74
- };
75
54
  target: RemoteSshBootstrapClaim['target'];
76
55
  }
56
+ type MetalAdmissionProvisioning = Pick<MetalBootstrapConfig['profile'], 'volumeGroup' | 'bridge' | 'subnetPrefix' | 'addressStart' | 'addressEnd' | 'gateway' | 'nameservers' | 'cpuPools' | 'housekeepingCpus'>;
57
+ /**
58
+ * Convert runner-observed Linux CPU topology into the fixed ForgeZero host
59
+ * boundary. The browser never authors this capacity. One physical core and all
60
+ * of its SMT siblings remain reserved for the host; every other complete core
61
+ * becomes the initial allocatable pool.
62
+ */
63
+ export declare function deriveMetalAdmissionProvisioning(lscpu: string): MetalAdmissionProvisioning;
77
64
  export interface MetalAdmissionProof {
78
65
  jobKey: string;
79
66
  tenantKey: string;
@@ -181,3 +168,4 @@ export declare function startMetalAdmissionPull(options: SshBootstrapPullOptions
181
168
  stop(): Promise<void>;
182
169
  readonly active: boolean;
183
170
  };
171
+ export {};
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** One package version shared by both public binaries. Pinned to package.json by tests. */
2
- export declare const VERSION = "0.1.128";
2
+ export declare const VERSION = "0.1.129";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.128",
3
+ "version": "0.1.129",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",
@@ -11,13 +11,13 @@
11
11
  "typescript": "^5.6.0",
12
12
  "@types/bun": "latest",
13
13
  "@types/node": "^22.0.0",
14
- "@forgezero/access": "0.1.13",
14
+ "@forgezero/access": "0.1.14",
15
15
  "@noble/curves": "^2.2.0",
16
16
  "@noble/post-quantum": "^0.6.1"
17
17
  },
18
18
  "dependencies": {
19
- "@forgezero/runtime": "0.1.19",
20
- "@forgezero/vault": "0.1.25",
19
+ "@forgezero/runtime": "0.1.20",
20
+ "@forgezero/vault": "0.1.26",
21
21
  "@noble/curves": "2.2.0",
22
22
  "@noble/hashes": "2.2.0",
23
23
  "@noble/post-quantum": "0.6.1",