@forgezero/agent 0.1.89 → 0.1.91

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.
@@ -37,6 +37,7 @@ export interface AgentHeartbeatResponse {
37
37
  ok: true;
38
38
  nodeKey: string;
39
39
  intervalSeconds: number;
40
+ workClaimsEnabled?: boolean;
40
41
  desiredAgentRelease?: AgentRelease;
41
42
  desiredAgentUpdate?: {
42
43
  attemptId: string;
@@ -70,6 +71,8 @@ export interface AgentHeartbeatOptions {
70
71
  deploymentIntakeObservation?: () => AgentObservation['deploymentIntake'];
71
72
  /** Apply the authenticated desired state before any new deployment claim is allowed. */
72
73
  applyDeploymentIntake?: (directive: NonNullable<AgentHeartbeatResponse['deploymentIntake']>) => Promise<void> | void;
74
+ /** Server lifecycle gate for deployment, migration and delegated bootstrap claims. */
75
+ applyWorkClaims?: (enabled: boolean) => Promise<void> | void;
73
76
  receiptPath?: string;
74
77
  now?: () => number;
75
78
  /** Stop new lifecycle/deploy claims and await current jobs after candidate readiness. */
@@ -917,7 +917,7 @@ async function postSignedNode(options, path, body) {
917
917
  }
918
918
 
919
919
  // src/version.ts
920
- var VERSION3 = "0.1.89";
920
+ var VERSION3 = "0.1.91";
921
921
 
922
922
  // src/agent-heartbeat.ts
923
923
  function readAgentHostMetrics() {
@@ -991,6 +991,7 @@ async function heartbeatAgentOnce(options) {
991
991
  throw new Error("agent heartbeat deployment-intake directive is invalid");
992
992
  await options.applyDeploymentIntake?.(response.deploymentIntake);
993
993
  }
994
+ await options.applyWorkClaims?.(response.workClaimsEnabled === true);
994
995
  const desired = response.desiredAgentUpdate ?? (response.desiredAgentRelease ? {
995
996
  attemptId: `legacy:${response.desiredAgentRelease.version}`,
996
997
  leaseExpiresAtTs: Number.MAX_SAFE_INTEGER,
package/dist/bootstrap.js CHANGED
@@ -1420,7 +1420,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1420
1420
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1421
1421
 
1422
1422
  // src/version.ts
1423
- var VERSION = "0.1.89";
1423
+ var VERSION = "0.1.91";
1424
1424
 
1425
1425
  // src/software.ts
1426
1426
  var PINNED_BUN_VERSION = "1.3.14";
@@ -2094,7 +2094,7 @@ function agentUnit(options) {
2094
2094
  if (!validNodeHostname(options.nodeHostname))
2095
2095
  throw new Error("node hostname is invalid");
2096
2096
  if (!options.telemetryEndpoint) {
2097
- throw new Error("compute Agent provisioning requires OTEL_EXPORTER_OTLP_ENDPOINT as an explicit public HTTPS collector coordinate");
2097
+ throw new Error("compute Agent provisioning requires an explicit supervised OTLP collector coordinate");
2098
2098
  }
2099
2099
  let telemetryEndpoint;
2100
2100
  {
@@ -2102,10 +2102,13 @@ function agentUnit(options) {
2102
2102
  try {
2103
2103
  endpoint2 = new URL(options.telemetryEndpoint);
2104
2104
  } catch {
2105
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL");
2105
+ throw new Error("compute telemetry endpoint must be an absolute collector URL");
2106
+ }
2107
+ const localCollector = endpoint2.protocol === "http:" && endpoint2.hostname === "127.0.0.1" && endpoint2.port === "4318" && endpoint2.pathname === "/";
2108
+ const publicCollector = endpoint2.protocol === "https:" && !endpoint2.username && !endpoint2.password && !endpoint2.search && !endpoint2.hash && isIP3(endpoint2.hostname) === 0 && endpoint2.hostname.includes(".") && endpoint2.hostname !== "localhost" && !endpoint2.hostname.endsWith(".local");
2109
+ if (!localCollector && !publicCollector || endpoint2.username || endpoint2.password || endpoint2.search || endpoint2.hash) {
2110
+ throw new Error("compute telemetry endpoint must be the supervised loopback collector or credential-free public HTTPS");
2106
2111
  }
2107
- if (endpoint2.protocol !== "https:" || endpoint2.username || endpoint2.password || endpoint2.search || endpoint2.hash || isIP3(endpoint2.hostname) !== 0 || !endpoint2.hostname.includes(".") || endpoint2.hostname === "localhost" || endpoint2.hostname.endsWith(".local"))
2108
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL without credentials, query or fragment");
2109
2112
  telemetryEndpoint = endpoint2.toString().replace(/\/$/, "");
2110
2113
  }
2111
2114
  const bin = options.binPath ?? "fz-agent";
@@ -4415,7 +4418,7 @@ async function bootstrapStatus(host = localBootstrapHost()) {
4415
4418
  if (result.exitCode !== 0)
4416
4419
  problems.push(`${unit} is not active`);
4417
4420
  }
4418
- for (const socket of [DEFAULT_SOCKET2, CONTROL_SOCKET]) {
4421
+ for (const socket of [DEFAULT_SOCKET2]) {
4419
4422
  services[socket] = host.exists(socket);
4420
4423
  if (!services[socket])
4421
4424
  problems.push(`${socket} is missing`);
@@ -4658,7 +4661,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4658
4661
  }
4659
4662
  let installedAgentPlan;
4660
4663
  if (alreadyEnrolled) {
4661
- installedAgentPlan = await host.installAgent(config, "bound");
4664
+ installedAgentPlan = await host.installAgent(config, bootstrapManifest ? "bootstrap" : "bound");
4662
4665
  } else if (config.kind === "platform") {
4663
4666
  if (bootstrapManifest)
4664
4667
  installedAgentPlan = await host.installAgent(config, "bootstrap");
@@ -4833,6 +4836,8 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4833
4836
  `, 384);
4834
4837
  host.remove(config.bootstrapBundle.bundleFile);
4835
4838
  host.remove(config.bootstrapBundle.manifestFile);
4839
+ if (alreadyEnrolled)
4840
+ installedAgentPlan = await host.installAgent(config, "bound");
4836
4841
  }
4837
4842
  if (!alreadyEnrolled) {
4838
4843
  await checked3(host, [
@@ -5044,8 +5049,8 @@ function readBootstrapConfig(path) {
5044
5049
  function planBootstrapAgentInstall(config, phase, context) {
5045
5050
  const { capabilities, hasBinding, hasEnrolCredential, initialBundle } = context;
5046
5051
  if (phase === "bootstrap") {
5047
- if (config.kind !== "platform" || hasBinding || !initialBundle) {
5048
- throw new Error("release-one Agent install requires an unbound platform host and verified bootstrap bundle");
5052
+ if (config.kind !== "platform" || !initialBundle) {
5053
+ throw new Error("platform bundle Agent install requires a verified bootstrap bundle");
5049
5054
  }
5050
5055
  } else if (phase === "enrol") {
5051
5056
  if (hasBinding || !hasEnrolCredential) {
@@ -5086,7 +5091,7 @@ function planBootstrapAgentInstall(config, phase, context) {
5086
5091
  runnerLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort, config.runtime.bluePort, config.runtime.greenPort] : [],
5087
5092
  agentLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort] : [],
5088
5093
  nodeHostname: config.nodeHostname,
5089
- telemetryEndpoint: config.telemetryEndpoint,
5094
+ telemetryEndpoint: config.kind === "platform" ? config.runtime.environment.otlpEndpoint : "http://127.0.0.1:4318",
5090
5095
  binPath: "/usr/local/lib/forgezero/agent/fz-agent",
5091
5096
  sourceBinPath: PACKAGED_AGENT_BIN,
5092
5097
  ...enrolling ? {
package/dist/fz-agent.js CHANGED
@@ -9459,6 +9459,10 @@ function startMigrationPull(options) {
9459
9459
  const tick = () => {
9460
9460
  if (stopped || active)
9461
9461
  return;
9462
+ if (options.canClaim && !options.canClaim()) {
9463
+ timer = setTimer(tick, interval);
9464
+ return;
9465
+ }
9462
9466
  active = pullMigrationOnce(options).then((result) => options.onEvent?.(result.status, result)).catch((cause) => options.onEvent?.("poll-failed", cause)).finally(() => {
9463
9467
  active = null;
9464
9468
  if (!stopped)
@@ -9492,7 +9496,7 @@ async function writeAndCloseProcessInput(input, value) {
9492
9496
  }
9493
9497
 
9494
9498
  // src/version.ts
9495
- var VERSION2 = "0.1.89";
9499
+ var VERSION2 = "0.1.91";
9496
9500
 
9497
9501
  // src/ssh-bootstrap.ts
9498
9502
  class SshBootstrapError extends Error {
@@ -9905,6 +9909,10 @@ function startSshBootstrapPull(options) {
9905
9909
  const tick = () => {
9906
9910
  if (stopped || active)
9907
9911
  return;
9912
+ if (options.canClaim && !options.canClaim()) {
9913
+ timer = setTimer(tick, interval);
9914
+ return;
9915
+ }
9908
9916
  active = pullSshBootstrapOnce(options).then((result) => options.onEvent?.(result.status, result.status === "idle" ? undefined : {
9909
9917
  computeKey: result.computeKey,
9910
9918
  attempt: result.attempt,
@@ -10176,6 +10184,10 @@ function startMetalAdmissionPull(options) {
10176
10184
  const tick = () => {
10177
10185
  if (stopped || active)
10178
10186
  return;
10187
+ if (options.canClaim && !options.canClaim()) {
10188
+ timer = setTimer(tick, interval);
10189
+ return;
10190
+ }
10179
10191
  active = pullMetalAdmissionOnce(options).then((result) => options.onEvent?.(`metal-admission-${result.status}`, result.status === "idle" ? undefined : result)).catch((cause) => options.onEvent?.("metal-admission-poll-failed", cause)).finally(() => {
10180
10192
  active = null;
10181
10193
  if (!stopped)
@@ -12732,6 +12744,7 @@ async function heartbeatAgentOnce(options) {
12732
12744
  throw new Error("agent heartbeat deployment-intake directive is invalid");
12733
12745
  await options.applyDeploymentIntake?.(response.deploymentIntake);
12734
12746
  }
12747
+ await options.applyWorkClaims?.(response.workClaimsEnabled === true);
12735
12748
  const desired = response.desiredAgentUpdate ?? (response.desiredAgentRelease ? {
12736
12749
  attemptId: `legacy:${response.desiredAgentRelease.version}`,
12737
12750
  leaseExpiresAtTs: Number.MAX_SAFE_INTEGER,
@@ -15628,7 +15641,7 @@ function agentUnit(options) {
15628
15641
  if (!validNodeHostname(options.nodeHostname))
15629
15642
  throw new Error("node hostname is invalid");
15630
15643
  if (!options.telemetryEndpoint) {
15631
- throw new Error("compute Agent provisioning requires OTEL_EXPORTER_OTLP_ENDPOINT as an explicit public HTTPS collector coordinate");
15644
+ throw new Error("compute Agent provisioning requires an explicit supervised OTLP collector coordinate");
15632
15645
  }
15633
15646
  let telemetryEndpoint;
15634
15647
  {
@@ -15636,10 +15649,13 @@ function agentUnit(options) {
15636
15649
  try {
15637
15650
  endpoint = new URL(options.telemetryEndpoint);
15638
15651
  } catch {
15639
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL");
15652
+ throw new Error("compute telemetry endpoint must be an absolute collector URL");
15653
+ }
15654
+ const localCollector = endpoint.protocol === "http:" && endpoint.hostname === "127.0.0.1" && endpoint.port === "4318" && endpoint.pathname === "/";
15655
+ const publicCollector = endpoint.protocol === "https:" && !endpoint.username && !endpoint.password && !endpoint.search && !endpoint.hash && isIP4(endpoint.hostname) === 0 && endpoint.hostname.includes(".") && endpoint.hostname !== "localhost" && !endpoint.hostname.endsWith(".local");
15656
+ if (!localCollector && !publicCollector || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {
15657
+ throw new Error("compute telemetry endpoint must be the supervised loopback collector or credential-free public HTTPS");
15640
15658
  }
15641
- if (endpoint.protocol !== "https:" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash || isIP4(endpoint.hostname) !== 0 || !endpoint.hostname.includes(".") || endpoint.hostname === "localhost" || endpoint.hostname.endsWith(".local"))
15642
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL without credentials, query or fragment");
15643
15659
  telemetryEndpoint = endpoint.toString().replace(/\/$/, "");
15644
15660
  }
15645
15661
  const bin = options.binPath ?? "fz-agent";
@@ -16565,7 +16581,7 @@ async function bootstrapStatus(host = localBootstrapHost()) {
16565
16581
  if (result.exitCode !== 0)
16566
16582
  problems.push(`${unit3} is not active`);
16567
16583
  }
16568
- for (const socket of [DEFAULT_SOCKET, CONTROL_SOCKET]) {
16584
+ for (const socket of [DEFAULT_SOCKET]) {
16569
16585
  services[socket] = host.exists(socket);
16570
16586
  if (!services[socket])
16571
16587
  problems.push(`${socket} is missing`);
@@ -16654,8 +16670,8 @@ async function bootstrapStatus(host = localBootstrapHost()) {
16654
16670
  function planBootstrapAgentInstall(config, phase, context) {
16655
16671
  const { capabilities, hasBinding, hasEnrolCredential, initialBundle } = context;
16656
16672
  if (phase === "bootstrap") {
16657
- if (config.kind !== "platform" || hasBinding || !initialBundle) {
16658
- throw new Error("release-one Agent install requires an unbound platform host and verified bootstrap bundle");
16673
+ if (config.kind !== "platform" || !initialBundle) {
16674
+ throw new Error("platform bundle Agent install requires a verified bootstrap bundle");
16659
16675
  }
16660
16676
  } else if (phase === "enrol") {
16661
16677
  if (hasBinding || !hasEnrolCredential) {
@@ -16696,7 +16712,7 @@ function planBootstrapAgentInstall(config, phase, context) {
16696
16712
  runnerLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort, config.runtime.bluePort, config.runtime.greenPort] : [],
16697
16713
  agentLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort] : [],
16698
16714
  nodeHostname: config.nodeHostname,
16699
- telemetryEndpoint: config.telemetryEndpoint,
16715
+ telemetryEndpoint: config.kind === "platform" ? config.runtime.environment.otlpEndpoint : "http://127.0.0.1:4318",
16700
16716
  binPath: "/usr/local/lib/forgezero/agent/fz-agent",
16701
16717
  sourceBinPath: PACKAGED_AGENT_BIN,
16702
16718
  ...enrolling ? {
@@ -18168,6 +18184,7 @@ if (import.meta.main) {
18168
18184
  if (attestationLoop)
18169
18185
  console.log("[agent] periodic SEV-SNP attestation enabled");
18170
18186
  const migrationEnabled = process.env.FZ_MIGRATION_PULL === "true";
18187
+ let workClaimsEnabled = false;
18171
18188
  if (migrationEnabled && (!binding || !nodeApiUrl)) {
18172
18189
  throw new Error("agent: outbound lifecycle work requires a persisted compute enrolment binding");
18173
18190
  }
@@ -18176,6 +18193,7 @@ if (import.meta.main) {
18176
18193
  nodeKey,
18177
18194
  keys,
18178
18195
  telemetry,
18196
+ canClaim: () => workClaimsEnabled,
18179
18197
  run: (claim) => requestLifecycleAction(claim, process.env.FZ_LIFECYCLE_HELPER_SOCKET ?? DEFAULT_LIFECYCLE_HELPER_SOCKET),
18180
18198
  onEvent: (event, detail) => console.log(`[agent] migration ${event}${detail ? ` ${JSON.stringify(detail)}` : ""}`)
18181
18199
  }) : undefined;
@@ -18194,6 +18212,7 @@ if (import.meta.main) {
18194
18212
  keys,
18195
18213
  sshKeyPath: bootstrapKeyPath,
18196
18214
  targetTelemetryEndpoint: process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT,
18215
+ canClaim: () => workClaimsEnabled,
18197
18216
  onEvent: (event) => console.log(`[agent] bootstrap ${event}`)
18198
18217
  }) : undefined;
18199
18218
  if (bootstrapPull)
@@ -18205,6 +18224,7 @@ if (import.meta.main) {
18205
18224
  keys,
18206
18225
  sshKeyPath: bootstrapKeyPath,
18207
18226
  targetTelemetryEndpoint: process.env.FZ_BOOTSTRAP_TARGET_OTLP_ENDPOINT,
18227
+ canClaim: () => workClaimsEnabled,
18208
18228
  onEvent: (event) => console.log(`[agent] ${event}`)
18209
18229
  }) : undefined;
18210
18230
  if (metalAdmissionPull)
@@ -18279,7 +18299,7 @@ if (import.meta.main) {
18279
18299
  nodeKey,
18280
18300
  keys,
18281
18301
  telemetry,
18282
- canClaim: () => deploymentIntake?.state === "running",
18302
+ canClaim: () => workClaimsEnabled && deploymentIntake?.state === "running",
18283
18303
  manager: staticManager,
18284
18304
  managerFor: staticManager ? undefined : (claim) => {
18285
18305
  if (claim.source.auth?.kind === "ephemeral-token") {
@@ -18317,6 +18337,12 @@ if (import.meta.main) {
18317
18337
  deploymentIntake = { ...directive, appliedAtTs: Date.now() };
18318
18338
  console.log(`[agent] deployment intake ${directive.state} generation ${directive.generation}`);
18319
18339
  },
18340
+ applyWorkClaims(enabled) {
18341
+ if (workClaimsEnabled === enabled)
18342
+ return;
18343
+ workClaimsEnabled = enabled;
18344
+ console.log(`[agent] remote work claims ${enabled ? "enabled" : "paused"} by platform lifecycle`);
18345
+ },
18320
18346
  async prepareUpdate() {
18321
18347
  if (updatePrepared)
18322
18348
  return;
package/dist/fz.js CHANGED
@@ -4840,7 +4840,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
4840
4840
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
4841
4841
 
4842
4842
  // src/version.ts
4843
- var VERSION2 = "0.1.89";
4843
+ var VERSION2 = "0.1.91";
4844
4844
 
4845
4845
  // src/software.ts
4846
4846
  var PINNED_BUN_VERSION = "1.3.14";
@@ -5885,7 +5885,7 @@ function agentUnit(options) {
5885
5885
  if (!validNodeHostname(options.nodeHostname))
5886
5886
  throw new Error("node hostname is invalid");
5887
5887
  if (!options.telemetryEndpoint) {
5888
- throw new Error("compute Agent provisioning requires OTEL_EXPORTER_OTLP_ENDPOINT as an explicit public HTTPS collector coordinate");
5888
+ throw new Error("compute Agent provisioning requires an explicit supervised OTLP collector coordinate");
5889
5889
  }
5890
5890
  let telemetryEndpoint;
5891
5891
  {
@@ -5893,10 +5893,13 @@ function agentUnit(options) {
5893
5893
  try {
5894
5894
  endpoint = new URL(options.telemetryEndpoint);
5895
5895
  } catch {
5896
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL");
5896
+ throw new Error("compute telemetry endpoint must be an absolute collector URL");
5897
+ }
5898
+ const localCollector = endpoint.protocol === "http:" && endpoint.hostname === "127.0.0.1" && endpoint.port === "4318" && endpoint.pathname === "/";
5899
+ const publicCollector = endpoint.protocol === "https:" && !endpoint.username && !endpoint.password && !endpoint.search && !endpoint.hash && isIP(endpoint.hostname) === 0 && endpoint.hostname.includes(".") && endpoint.hostname !== "localhost" && !endpoint.hostname.endsWith(".local");
5900
+ if (!localCollector && !publicCollector || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {
5901
+ throw new Error("compute telemetry endpoint must be the supervised loopback collector or credential-free public HTTPS");
5897
5902
  }
5898
- if (endpoint.protocol !== "https:" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash || isIP(endpoint.hostname) !== 0 || !endpoint.hostname.includes(".") || endpoint.hostname === "localhost" || endpoint.hostname.endsWith(".local"))
5899
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL without credentials, query or fragment");
5900
5903
  telemetryEndpoint = endpoint.toString().replace(/\/$/, "");
5901
5904
  }
5902
5905
  const bin = options.binPath ?? "fz-agent";
@@ -14799,7 +14802,7 @@ async function bootstrapStatus(host = localBootstrapHost()) {
14799
14802
  if (result.exitCode !== 0)
14800
14803
  problems.push(`${unit} is not active`);
14801
14804
  }
14802
- for (const socket of [DEFAULT_SOCKET, CONTROL_SOCKET]) {
14805
+ for (const socket of [DEFAULT_SOCKET]) {
14803
14806
  services[socket] = host.exists(socket);
14804
14807
  if (!services[socket])
14805
14808
  problems.push(`${socket} is missing`);
@@ -15042,7 +15045,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
15042
15045
  }
15043
15046
  let installedAgentPlan;
15044
15047
  if (alreadyEnrolled) {
15045
- installedAgentPlan = await host.installAgent(config, "bound");
15048
+ installedAgentPlan = await host.installAgent(config, bootstrapManifest ? "bootstrap" : "bound");
15046
15049
  } else if (config.kind === "platform") {
15047
15050
  if (bootstrapManifest)
15048
15051
  installedAgentPlan = await host.installAgent(config, "bootstrap");
@@ -15217,6 +15220,8 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
15217
15220
  `, 384);
15218
15221
  host.remove(config.bootstrapBundle.bundleFile);
15219
15222
  host.remove(config.bootstrapBundle.manifestFile);
15223
+ if (alreadyEnrolled)
15224
+ installedAgentPlan = await host.installAgent(config, "bound");
15220
15225
  }
15221
15226
  if (!alreadyEnrolled) {
15222
15227
  await checked3(host, [
@@ -15428,8 +15433,8 @@ function readBootstrapConfig(path) {
15428
15433
  function planBootstrapAgentInstall(config, phase, context) {
15429
15434
  const { capabilities, hasBinding, hasEnrolCredential, initialBundle } = context;
15430
15435
  if (phase === "bootstrap") {
15431
- if (config.kind !== "platform" || hasBinding || !initialBundle) {
15432
- throw new Error("release-one Agent install requires an unbound platform host and verified bootstrap bundle");
15436
+ if (config.kind !== "platform" || !initialBundle) {
15437
+ throw new Error("platform bundle Agent install requires a verified bootstrap bundle");
15433
15438
  }
15434
15439
  } else if (phase === "enrol") {
15435
15440
  if (hasBinding || !hasEnrolCredential) {
@@ -15470,7 +15475,7 @@ function planBootstrapAgentInstall(config, phase, context) {
15470
15475
  runnerLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort, config.runtime.bluePort, config.runtime.greenPort] : [],
15471
15476
  agentLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort] : [],
15472
15477
  nodeHostname: config.nodeHostname,
15473
- telemetryEndpoint: config.telemetryEndpoint,
15478
+ telemetryEndpoint: config.kind === "platform" ? config.runtime.environment.otlpEndpoint : "http://127.0.0.1:4318",
15474
15479
  binPath: "/usr/local/lib/forgezero/agent/fz-agent",
15475
15480
  sourceBinPath: PACKAGED_AGENT_BIN,
15476
15481
  ...enrolling ? {
@@ -17416,7 +17421,7 @@ var remoteRegularFileExists = async (exec, request, knownHosts, path) => {
17416
17421
  throw new Error(`remote initialized-state probe failed${result.output ? `: ${result.output.trim()}` : ""}`);
17417
17422
  };
17418
17423
  async function operatorMetalClusterBootstrapCode(requestInput, options = {}) {
17419
- const request = validateMetalRequestValue(requestInput);
17424
+ const request = validateMetalRequestValue(requestInput, { validateMetalConfig: false });
17420
17425
  publicIdentity(request.target.identityPublicKeyFile);
17421
17426
  socketPath(request.target.agentSocket);
17422
17427
  const exec = options.exec ?? defaultExec3;
@@ -17912,6 +17917,7 @@ var PROFILES = {
17912
17917
  kvNamespaceTitle: "forgezero dev nodes",
17913
17918
  workerScriptName: "forgezero-api-edge-development",
17914
17919
  region: { key: "in-south", label: "India South", country: "IN" },
17920
+ seedSyncEpoch: "development-23c19168773d69ea",
17915
17921
  agentOtlpEndpoint: "https://otel.forgezero.net",
17916
17922
  privateCidrs: ["10.42.0.0/24"]
17917
17923
  },
@@ -17924,6 +17930,7 @@ var PROFILES = {
17924
17930
  kvNamespaceTitle: "forgezero nodes",
17925
17931
  workerScriptName: "forgezero-api-edge-production",
17926
17932
  region: { key: "in-south", label: "India South", country: "IN" },
17933
+ seedSyncEpoch: "production-genesis-v1",
17927
17934
  agentOtlpEndpoint: "https://otel.forgezero.net",
17928
17935
  privateCidrs: ["10.42.0.0/24"]
17929
17936
  }
@@ -17991,7 +17998,7 @@ function forgeZeroLaunchTemplate(profile, guests, bundle, owner) {
17991
17998
  sharedDirectory: "/opt/forgezero/shared",
17992
17999
  seedSyncPeers: fleet.slice(1).flatMap(({ address }) => [3001, 3002].map((port) => `ws://${address}:${port}/_internal/forgezero/seed-mesh/v2`)),
17993
18000
  seedSyncMembers: nodeHostnames,
17994
- seedSyncEpoch: `${profile.environment}-${bundle.manifest.revision.slice(0, 16)}`,
18001
+ seedSyncEpoch: profile.seedSyncEpoch,
17995
18002
  concurrencyLimit: 128,
17996
18003
  drainDeadlineMs: 30000,
17997
18004
  otlpEndpoint: "http://127.0.0.1:4318",
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
366
366
  }
367
367
 
368
368
  // src/version.ts
369
- var VERSION = "0.1.89";
369
+ var VERSION = "0.1.91";
370
370
 
371
371
  // src/otel-collector.ts
372
372
  var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
@@ -39,6 +39,7 @@ export interface MigrationPullOptions {
39
39
  clearTimer?: (handle: unknown) => void;
40
40
  onEvent?: (event: string, detail?: unknown) => void;
41
41
  telemetry?: AgentOperationTelemetry;
42
+ canClaim?: () => boolean;
42
43
  }
43
44
  export type MigrationPullResult = {
44
45
  status: 'idle';
@@ -188,6 +188,10 @@ function startMigrationPull(options) {
188
188
  const tick = () => {
189
189
  if (stopped || active)
190
190
  return;
191
+ if (options.canClaim && !options.canClaim()) {
192
+ timer = setTimer(tick, interval);
193
+ return;
194
+ }
191
195
  active = pullMigrationOnce(options).then((result) => options.onEvent?.(result.status, result)).catch((cause) => options.onEvent?.("poll-failed", cause)).finally(() => {
192
196
  active = null;
193
197
  if (!stopped)
@@ -1420,7 +1420,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1420
1420
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1421
1421
 
1422
1422
  // src/version.ts
1423
- var VERSION = "0.1.89";
1423
+ var VERSION = "0.1.91";
1424
1424
 
1425
1425
  // src/software.ts
1426
1426
  var PINNED_BUN_VERSION = "1.3.14";
@@ -2094,7 +2094,7 @@ function agentUnit(options) {
2094
2094
  if (!validNodeHostname(options.nodeHostname))
2095
2095
  throw new Error("node hostname is invalid");
2096
2096
  if (!options.telemetryEndpoint) {
2097
- throw new Error("compute Agent provisioning requires OTEL_EXPORTER_OTLP_ENDPOINT as an explicit public HTTPS collector coordinate");
2097
+ throw new Error("compute Agent provisioning requires an explicit supervised OTLP collector coordinate");
2098
2098
  }
2099
2099
  let telemetryEndpoint;
2100
2100
  {
@@ -2102,10 +2102,13 @@ function agentUnit(options) {
2102
2102
  try {
2103
2103
  endpoint2 = new URL(options.telemetryEndpoint);
2104
2104
  } catch {
2105
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL");
2105
+ throw new Error("compute telemetry endpoint must be an absolute collector URL");
2106
+ }
2107
+ const localCollector = endpoint2.protocol === "http:" && endpoint2.hostname === "127.0.0.1" && endpoint2.port === "4318" && endpoint2.pathname === "/";
2108
+ const publicCollector = endpoint2.protocol === "https:" && !endpoint2.username && !endpoint2.password && !endpoint2.search && !endpoint2.hash && isIP3(endpoint2.hostname) === 0 && endpoint2.hostname.includes(".") && endpoint2.hostname !== "localhost" && !endpoint2.hostname.endsWith(".local");
2109
+ if (!localCollector && !publicCollector || endpoint2.username || endpoint2.password || endpoint2.search || endpoint2.hash) {
2110
+ throw new Error("compute telemetry endpoint must be the supervised loopback collector or credential-free public HTTPS");
2106
2111
  }
2107
- if (endpoint2.protocol !== "https:" || endpoint2.username || endpoint2.password || endpoint2.search || endpoint2.hash || isIP3(endpoint2.hostname) !== 0 || !endpoint2.hostname.includes(".") || endpoint2.hostname === "localhost" || endpoint2.hostname.endsWith(".local"))
2108
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL without credentials, query or fragment");
2109
2112
  telemetryEndpoint = endpoint2.toString().replace(/\/$/, "");
2110
2113
  }
2111
2114
  const bin = options.binPath ?? "fz-agent";
@@ -4415,7 +4418,7 @@ async function bootstrapStatus(host = localBootstrapHost()) {
4415
4418
  if (result.exitCode !== 0)
4416
4419
  problems.push(`${unit} is not active`);
4417
4420
  }
4418
- for (const socket of [DEFAULT_SOCKET2, CONTROL_SOCKET]) {
4421
+ for (const socket of [DEFAULT_SOCKET2]) {
4419
4422
  services[socket] = host.exists(socket);
4420
4423
  if (!services[socket])
4421
4424
  problems.push(`${socket} is missing`);
@@ -4658,7 +4661,7 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4658
4661
  }
4659
4662
  let installedAgentPlan;
4660
4663
  if (alreadyEnrolled) {
4661
- installedAgentPlan = await host.installAgent(config, "bound");
4664
+ installedAgentPlan = await host.installAgent(config, bootstrapManifest ? "bootstrap" : "bound");
4662
4665
  } else if (config.kind === "platform") {
4663
4666
  if (bootstrapManifest)
4664
4667
  installedAgentPlan = await host.installAgent(config, "bootstrap");
@@ -4833,6 +4836,8 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4833
4836
  `, 384);
4834
4837
  host.remove(config.bootstrapBundle.bundleFile);
4835
4838
  host.remove(config.bootstrapBundle.manifestFile);
4839
+ if (alreadyEnrolled)
4840
+ installedAgentPlan = await host.installAgent(config, "bound");
4836
4841
  }
4837
4842
  if (!alreadyEnrolled) {
4838
4843
  await checked3(host, [
@@ -5044,8 +5049,8 @@ function readBootstrapConfig(path) {
5044
5049
  function planBootstrapAgentInstall(config, phase, context) {
5045
5050
  const { capabilities, hasBinding, hasEnrolCredential, initialBundle } = context;
5046
5051
  if (phase === "bootstrap") {
5047
- if (config.kind !== "platform" || hasBinding || !initialBundle) {
5048
- throw new Error("release-one Agent install requires an unbound platform host and verified bootstrap bundle");
5052
+ if (config.kind !== "platform" || !initialBundle) {
5053
+ throw new Error("platform bundle Agent install requires a verified bootstrap bundle");
5049
5054
  }
5050
5055
  } else if (phase === "enrol") {
5051
5056
  if (hasBinding || !hasEnrolCredential) {
@@ -5086,7 +5091,7 @@ function planBootstrapAgentInstall(config, phase, context) {
5086
5091
  runnerLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort, config.runtime.bluePort, config.runtime.greenPort] : [],
5087
5092
  agentLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort] : [],
5088
5093
  nodeHostname: config.nodeHostname,
5089
- telemetryEndpoint: config.telemetryEndpoint,
5094
+ telemetryEndpoint: config.kind === "platform" ? config.runtime.environment.otlpEndpoint : "http://127.0.0.1:4318",
5090
5095
  binPath: "/usr/local/lib/forgezero/agent/fz-agent",
5091
5096
  sourceBinPath: PACKAGED_AGENT_BIN,
5092
5097
  ...enrolling ? {
@@ -6768,7 +6773,7 @@ var remoteRegularFileExists = async (exec, request, knownHosts, path) => {
6768
6773
  throw new Error(`remote initialized-state probe failed${result.output ? `: ${result.output.trim()}` : ""}`);
6769
6774
  };
6770
6775
  async function operatorMetalClusterBootstrapCode(requestInput, options = {}) {
6771
- const request = validateMetalRequestValue(requestInput);
6776
+ const request = validateMetalRequestValue(requestInput, { validateMetalConfig: false });
6772
6777
  publicIdentity(request.target.identityPublicKeyFile);
6773
6778
  socketPath(request.target.agentSocket);
6774
6779
  const exec = options.exec ?? defaultExec3;
@@ -2908,7 +2908,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
2908
2908
  }
2909
2909
 
2910
2910
  // src/version.ts
2911
- var VERSION3 = "0.1.89";
2911
+ var VERSION3 = "0.1.91";
2912
2912
 
2913
2913
  // src/egress-policy.ts
2914
2914
  import { realpathSync as realpathSync3 } from "node:fs";
@@ -3440,7 +3440,7 @@ function agentUnit(options) {
3440
3440
  if (!validNodeHostname(options.nodeHostname))
3441
3441
  throw new Error("node hostname is invalid");
3442
3442
  if (!options.telemetryEndpoint) {
3443
- throw new Error("compute Agent provisioning requires OTEL_EXPORTER_OTLP_ENDPOINT as an explicit public HTTPS collector coordinate");
3443
+ throw new Error("compute Agent provisioning requires an explicit supervised OTLP collector coordinate");
3444
3444
  }
3445
3445
  let telemetryEndpoint;
3446
3446
  {
@@ -3448,10 +3448,13 @@ function agentUnit(options) {
3448
3448
  try {
3449
3449
  endpoint = new URL(options.telemetryEndpoint);
3450
3450
  } catch {
3451
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL");
3451
+ throw new Error("compute telemetry endpoint must be an absolute collector URL");
3452
+ }
3453
+ const localCollector = endpoint.protocol === "http:" && endpoint.hostname === "127.0.0.1" && endpoint.port === "4318" && endpoint.pathname === "/";
3454
+ const publicCollector = endpoint.protocol === "https:" && !endpoint.username && !endpoint.password && !endpoint.search && !endpoint.hash && isIP(endpoint.hostname) === 0 && endpoint.hostname.includes(".") && endpoint.hostname !== "localhost" && !endpoint.hostname.endsWith(".local");
3455
+ if (!localCollector && !publicCollector || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {
3456
+ throw new Error("compute telemetry endpoint must be the supervised loopback collector or credential-free public HTTPS");
3452
3457
  }
3453
- if (endpoint.protocol !== "https:" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash || isIP(endpoint.hostname) !== 0 || !endpoint.hostname.includes(".") || endpoint.hostname === "localhost" || endpoint.hostname.endsWith(".local"))
3454
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL without credentials, query or fragment");
3455
3458
  telemetryEndpoint = endpoint.toString().replace(/\/$/, "");
3456
3459
  }
3457
3460
  const bin = options.binPath ?? "fz-agent";
@@ -5220,7 +5223,7 @@ async function bootstrapStatus(host = localBootstrapHost()) {
5220
5223
  if (result.exitCode !== 0)
5221
5224
  problems.push(`${unit2} is not active`);
5222
5225
  }
5223
- for (const socket of [DEFAULT_SOCKET2, CONTROL_SOCKET]) {
5226
+ for (const socket of [DEFAULT_SOCKET2]) {
5224
5227
  services[socket] = host.exists(socket);
5225
5228
  if (!services[socket])
5226
5229
  problems.push(`${socket} is missing`);
@@ -5309,8 +5312,8 @@ async function bootstrapStatus(host = localBootstrapHost()) {
5309
5312
  function planBootstrapAgentInstall(config, phase, context) {
5310
5313
  const { capabilities, hasBinding, hasEnrolCredential, initialBundle } = context;
5311
5314
  if (phase === "bootstrap") {
5312
- if (config.kind !== "platform" || hasBinding || !initialBundle) {
5313
- throw new Error("release-one Agent install requires an unbound platform host and verified bootstrap bundle");
5315
+ if (config.kind !== "platform" || !initialBundle) {
5316
+ throw new Error("platform bundle Agent install requires a verified bootstrap bundle");
5314
5317
  }
5315
5318
  } else if (phase === "enrol") {
5316
5319
  if (hasBinding || !hasEnrolCredential) {
@@ -5351,7 +5354,7 @@ function planBootstrapAgentInstall(config, phase, context) {
5351
5354
  runnerLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort, config.runtime.bluePort, config.runtime.greenPort] : [],
5352
5355
  agentLoopbackPorts: config.kind === "platform" ? [config.runtime.environment.publicApiPort] : [],
5353
5356
  nodeHostname: config.nodeHostname,
5354
- telemetryEndpoint: config.telemetryEndpoint,
5357
+ telemetryEndpoint: config.kind === "platform" ? config.runtime.environment.otlpEndpoint : "http://127.0.0.1:4318",
5355
5358
  binPath: "/usr/local/lib/forgezero/agent/fz-agent",
5356
5359
  sourceBinPath: PACKAGED_AGENT_BIN,
5357
5360
  ...enrolling ? {
@@ -17,6 +17,7 @@ export interface ForgeZeroLaunchProfile {
17
17
  city?: string;
18
18
  };
19
19
  agentOtlpEndpoint: string;
20
+ seedSyncEpoch: string;
20
21
  privateCidrs: readonly string[];
21
22
  nodeHostname(nodeIndex: number): string;
22
23
  }
package/dist/provision.js CHANGED
@@ -2908,7 +2908,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
2908
2908
  }
2909
2909
 
2910
2910
  // src/version.ts
2911
- var VERSION3 = "0.1.89";
2911
+ var VERSION3 = "0.1.91";
2912
2912
 
2913
2913
  // src/egress-policy.ts
2914
2914
  import { realpathSync as realpathSync3 } from "node:fs";
@@ -3440,7 +3440,7 @@ function agentUnit(options) {
3440
3440
  if (!validNodeHostname(options.nodeHostname))
3441
3441
  throw new Error("node hostname is invalid");
3442
3442
  if (!options.telemetryEndpoint) {
3443
- throw new Error("compute Agent provisioning requires OTEL_EXPORTER_OTLP_ENDPOINT as an explicit public HTTPS collector coordinate");
3443
+ throw new Error("compute Agent provisioning requires an explicit supervised OTLP collector coordinate");
3444
3444
  }
3445
3445
  let telemetryEndpoint;
3446
3446
  {
@@ -3448,10 +3448,13 @@ function agentUnit(options) {
3448
3448
  try {
3449
3449
  endpoint = new URL(options.telemetryEndpoint);
3450
3450
  } catch {
3451
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL");
3451
+ throw new Error("compute telemetry endpoint must be an absolute collector URL");
3452
+ }
3453
+ const localCollector = endpoint.protocol === "http:" && endpoint.hostname === "127.0.0.1" && endpoint.port === "4318" && endpoint.pathname === "/";
3454
+ const publicCollector = endpoint.protocol === "https:" && !endpoint.username && !endpoint.password && !endpoint.search && !endpoint.hash && isIP(endpoint.hostname) === 0 && endpoint.hostname.includes(".") && endpoint.hostname !== "localhost" && !endpoint.hostname.endsWith(".local");
3455
+ if (!localCollector && !publicCollector || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {
3456
+ throw new Error("compute telemetry endpoint must be the supervised loopback collector or credential-free public HTTPS");
3452
3457
  }
3453
- if (endpoint.protocol !== "https:" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash || isIP(endpoint.hostname) !== 0 || !endpoint.hostname.includes(".") || endpoint.hostname === "localhost" || endpoint.hostname.endsWith(".local"))
3454
- throw new Error("compute telemetry endpoint must be an absolute public HTTPS URL without credentials, query or fragment");
3455
3458
  telemetryEndpoint = endpoint.toString().replace(/\/$/, "");
3456
3459
  }
3457
3460
  const bin = options.binPath ?? "fz-agent";
@@ -129,6 +129,7 @@ export interface SshBootstrapPullOptions {
129
129
  onEvent?: (event: string, detail?: unknown) => void;
130
130
  telemetry?: AgentOperationTelemetry;
131
131
  resolveHost?: GitHostResolver;
132
+ canClaim?: () => boolean;
132
133
  }
133
134
  export type SshBootstrapResult = {
134
135
  status: 'idle';
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.89";
2
+ export declare const VERSION = "0.1.91";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.89",
3
+ "version": "0.1.91",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",