@forgezero/agent 0.1.143 → 0.1.145

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.
@@ -917,7 +917,7 @@ async function postSignedNode(options, path, body) {
917
917
  }
918
918
 
919
919
  // src/version.ts
920
- var VERSION3 = "0.1.143";
920
+ var VERSION3 = "0.1.145";
921
921
 
922
922
  // src/agent-heartbeat.ts
923
923
  function readAgentHostMetrics() {
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.143";
1463
+ var VERSION = "0.1.145";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -3555,6 +3555,9 @@ function renderPlatformActivationFiles(input) {
3555
3555
  boundedInteger("greenPort", input.greenPort, 1024, 65535);
3556
3556
  if (input.bluePort === input.greenPort)
3557
3557
  throw new Error("Activation slot ports must differ.");
3558
+ if (!/^[A-Za-z0-9.-]{1,253}$/.test(input.seedMeshAddress) || input.seedMeshAddress.startsWith(".") || input.seedMeshAddress.endsWith(".")) {
3559
+ throw new Error("Activation seed mesh address is malformed.");
3560
+ }
3558
3561
  boundedInteger("keepReleases", input.keepReleases, 2, 100);
3559
3562
  const drainDeadlineMs = boundedInteger("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
3560
3563
  if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
@@ -3565,6 +3568,7 @@ function renderPlatformActivationFiles(input) {
3565
3568
  `FZ_USER=${input.serviceUser}`,
3566
3569
  `FZ_BLUE_PORT=${input.bluePort}`,
3567
3570
  `FZ_GREEN_PORT=${input.greenPort}`,
3571
+ `FZ_SEED_MESH_ADDRESS=${input.seedMeshAddress}`,
3568
3572
  `FZ_HEALTH_PATH=${input.healthPath}`,
3569
3573
  `FZ_KEEP_RELEASES=${input.keepReleases}`,
3570
3574
  `FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
@@ -4937,11 +4941,15 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4937
4941
  concurrencyLimit: runtime.environment.concurrencyLimit,
4938
4942
  workerDrainSeconds: Math.ceil(runtime.environment.drainDeadlineMs / 1000)
4939
4943
  });
4944
+ const seedMeshAddress = runtime.environment.databaseAddress ?? config.database.address;
4945
+ if (!seedMeshAddress)
4946
+ throw new Error("platform activation requires the private seed mesh address");
4940
4947
  const activation = renderPlatformActivationFiles({
4941
4948
  root: config.deployRoot ?? "/opt/forgezero",
4942
4949
  serviceUser: runtime.serviceUser,
4943
4950
  bluePort: runtime.bluePort,
4944
4951
  greenPort: runtime.greenPort,
4952
+ seedMeshAddress,
4945
4953
  healthPath: runtime.healthPath,
4946
4954
  keepReleases: runtime.keepReleases,
4947
4955
  drainDeadlineMs: runtime.environment.drainDeadlineMs
package/dist/fz-agent.js CHANGED
@@ -9684,7 +9684,7 @@ async function writeAndCloseProcessInput(input, value) {
9684
9684
  }
9685
9685
 
9686
9686
  // src/version.ts
9687
- var VERSION2 = "0.1.143";
9687
+ var VERSION2 = "0.1.145";
9688
9688
 
9689
9689
  // src/ssh-bootstrap.ts
9690
9690
  function deriveMetalAdmissionProvisioning(lscpu) {
@@ -15340,6 +15340,9 @@ function renderPlatformActivationFiles(input) {
15340
15340
  boundedInteger2("greenPort", input.greenPort, 1024, 65535);
15341
15341
  if (input.bluePort === input.greenPort)
15342
15342
  throw new Error("Activation slot ports must differ.");
15343
+ if (!/^[A-Za-z0-9.-]{1,253}$/.test(input.seedMeshAddress) || input.seedMeshAddress.startsWith(".") || input.seedMeshAddress.endsWith(".")) {
15344
+ throw new Error("Activation seed mesh address is malformed.");
15345
+ }
15343
15346
  boundedInteger2("keepReleases", input.keepReleases, 2, 100);
15344
15347
  const drainDeadlineMs = boundedInteger2("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
15345
15348
  if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
@@ -15350,6 +15353,7 @@ function renderPlatformActivationFiles(input) {
15350
15353
  `FZ_USER=${input.serviceUser}`,
15351
15354
  `FZ_BLUE_PORT=${input.bluePort}`,
15352
15355
  `FZ_GREEN_PORT=${input.greenPort}`,
15356
+ `FZ_SEED_MESH_ADDRESS=${input.seedMeshAddress}`,
15353
15357
  `FZ_HEALTH_PATH=${input.healthPath}`,
15354
15358
  `FZ_KEEP_RELEASES=${input.keepReleases}`,
15355
15359
  `FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
@@ -15446,6 +15450,15 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
15446
15450
  }
15447
15451
  const target2 = previousSlot === "blue" ? "green" : "blue";
15448
15452
  const port = target2 === "blue" ? normalized.bluePort : normalized.greenPort;
15453
+ const meshProbe = async (slotPort, path2) => {
15454
+ try {
15455
+ const response = await request(`http://${normalized.seedMeshAddress}:${slotPort}/_internal/forgezero/seed-mesh/${path2}`, { signal: AbortSignal.timeout(2000) });
15456
+ return response.ok;
15457
+ } catch {
15458
+ return false;
15459
+ }
15460
+ };
15461
+ const previousHeldSeed = previousSlot ? await meshProbe(previousSlot === "blue" ? normalized.bluePort : normalized.greenPort, "resident") : false;
15449
15462
  const targetLink = join10(slots, target2);
15450
15463
  let previousTarget;
15451
15464
  try {
@@ -15483,6 +15496,19 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
15483
15496
  await stopTarget();
15484
15497
  throw new Error("target slot failed its health deadline");
15485
15498
  }
15499
+ if (previousHeldSeed) {
15500
+ let seedReady = false;
15501
+ for (let attempt = 0;attempt < 30; attempt += 1) {
15502
+ seedReady = await meshProbe(port, "ready");
15503
+ if (seedReady)
15504
+ break;
15505
+ await sleep2(1000);
15506
+ }
15507
+ if (!seedReady) {
15508
+ await stopTarget();
15509
+ throw new Error("target slot failed RAM-seed handover deadline");
15510
+ }
15511
+ }
15486
15512
  const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
15487
15513
  const backup = `${upstream}.forgezero-backup`;
15488
15514
  if (existsSync19(upstream))
package/dist/fz.js CHANGED
@@ -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.143";
6010
+ var VERSION2 = "0.1.145";
6011
6011
 
6012
6012
  // src/software.ts
6013
6013
  var PINNED_BUN_VERSION = "1.3.14";
@@ -12922,6 +12922,9 @@ function renderPlatformActivationFiles(input) {
12922
12922
  boundedInteger("greenPort", input.greenPort, 1024, 65535);
12923
12923
  if (input.bluePort === input.greenPort)
12924
12924
  throw new Error("Activation slot ports must differ.");
12925
+ if (!/^[A-Za-z0-9.-]{1,253}$/.test(input.seedMeshAddress) || input.seedMeshAddress.startsWith(".") || input.seedMeshAddress.endsWith(".")) {
12926
+ throw new Error("Activation seed mesh address is malformed.");
12927
+ }
12925
12928
  boundedInteger("keepReleases", input.keepReleases, 2, 100);
12926
12929
  const drainDeadlineMs = boundedInteger("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
12927
12930
  if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
@@ -12932,6 +12935,7 @@ function renderPlatformActivationFiles(input) {
12932
12935
  `FZ_USER=${input.serviceUser}`,
12933
12936
  `FZ_BLUE_PORT=${input.bluePort}`,
12934
12937
  `FZ_GREEN_PORT=${input.greenPort}`,
12938
+ `FZ_SEED_MESH_ADDRESS=${input.seedMeshAddress}`,
12935
12939
  `FZ_HEALTH_PATH=${input.healthPath}`,
12936
12940
  `FZ_KEEP_RELEASES=${input.keepReleases}`,
12937
12941
  `FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
@@ -15580,11 +15584,15 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
15580
15584
  concurrencyLimit: runtime.environment.concurrencyLimit,
15581
15585
  workerDrainSeconds: Math.ceil(runtime.environment.drainDeadlineMs / 1000)
15582
15586
  });
15587
+ const seedMeshAddress = runtime.environment.databaseAddress ?? config.database.address;
15588
+ if (!seedMeshAddress)
15589
+ throw new Error("platform activation requires the private seed mesh address");
15583
15590
  const activation = renderPlatformActivationFiles({
15584
15591
  root: config.deployRoot ?? "/opt/forgezero",
15585
15592
  serviceUser: runtime.serviceUser,
15586
15593
  bluePort: runtime.bluePort,
15587
15594
  greenPort: runtime.greenPort,
15595
+ seedMeshAddress,
15588
15596
  healthPath: runtime.healthPath,
15589
15597
  keepReleases: runtime.keepReleases,
15590
15598
  drainDeadlineMs: runtime.environment.drainDeadlineMs
@@ -18287,7 +18295,21 @@ async function applyOperatorPlatformFleetBootstrap(fleet, mode, options = {}) {
18287
18295
  for (const config of configs.slice(1))
18288
18296
  validatePlatformBootstrapSecrets(config, secrets);
18289
18297
  }
18290
- const settled = await Promise.allSettled(requests.map((request) => applyOperatorPlatformBootstrap(request, mode, { ...options, secrets })));
18298
+ const settled = [];
18299
+ if (mode === "apply") {
18300
+ settled.push(...await Promise.allSettled(requests.map((request) => applyOperatorPlatformBootstrap(request, mode, { ...options, secrets }))));
18301
+ } else {
18302
+ for (const request of requests) {
18303
+ try {
18304
+ settled.push({
18305
+ status: "fulfilled",
18306
+ value: await applyOperatorPlatformBootstrap(request, mode, { ...options, secrets })
18307
+ });
18308
+ } catch (reason) {
18309
+ settled.push({ status: "rejected", reason });
18310
+ }
18311
+ }
18312
+ }
18291
18313
  const outcomes = settled.map((result, index) => {
18292
18314
  const config = configs[index];
18293
18315
  const address = requests[index].target.address;
@@ -18531,7 +18553,7 @@ function platformGenesisBootstrapConfigs(template, guests, nodes, metalHostname,
18531
18553
  databaseAddress: guest.address,
18532
18554
  databaseMaster: role === "joiner" ? master : undefined,
18533
18555
  nodeHostname: node.nodeHostname,
18534
- seedSyncPeers: fleet.filter((_, peer) => peer !== index).flatMap((peer) => [config.runtime.bluePort, config.runtime.greenPort].map((port) => `ws://${peer.address}:${port}/_internal/forgezero/seed-mesh/v2`)),
18556
+ seedSyncPeers: fleet.flatMap((peer) => [config.runtime.bluePort, config.runtime.greenPort].map((port) => `ws://${peer.address}:${port}/_internal/forgezero/seed-mesh/v2`)),
18535
18557
  seedSyncMembers: nodes.map(({ nodeHostname }) => nodeHostname),
18536
18558
  initialInventory,
18537
18559
  custodianEmail: template.runtime.environment.custodianEmail
@@ -18636,7 +18658,7 @@ function forgeZeroLaunchTemplate(profile, guests, bundle, owner) {
18636
18658
  apiOrigin: profile.apiOrigin,
18637
18659
  publicApiPort: 3000,
18638
18660
  sharedDirectory: "/opt/forgezero/shared",
18639
- seedSyncPeers: fleet.slice(1).flatMap(({ address }) => [3001, 3002].map((port) => `ws://${address}:${port}/_internal/forgezero/seed-mesh/v2`)),
18661
+ seedSyncPeers: fleet.flatMap(({ address }) => [3001, 3002].map((port) => `ws://${address}:${port}/_internal/forgezero/seed-mesh/v2`)),
18640
18662
  seedSyncMembers: nodeHostnames,
18641
18663
  seedSyncEpoch: profile.seedSyncEpoch,
18642
18664
  concurrencyLimit: 128,
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
366
366
  }
367
367
 
368
368
  // src/version.ts
369
- var VERSION = "0.1.143";
369
+ var VERSION = "0.1.145";
370
370
 
371
371
  // src/otel-collector.ts
372
372
  var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
@@ -191,9 +191,12 @@ export declare function applyOperatorPlatformBootstrap(request: OperatorPlatform
191
191
  output: string;
192
192
  }>;
193
193
  /**
194
- * Run the exact three-node genesis fleet from one attended secret collection.
195
- * Every request is fully validated before the first SSH connection. Nodes run
196
- * concurrently because the three-member Agency cannot form sequentially.
194
+ * Run the exact three-node fleet from one attended secret collection.
195
+ * Every request is fully validated before the first SSH connection. Initial
196
+ * genesis runs concurrently because the three-member Agency must form as one
197
+ * operation. An established fleet is repaired/promoted one node at a time so
198
+ * an API origin and an unlocked seed-mesh peer remain resident throughout the
199
+ * rollout.
197
200
  */
198
201
  export declare function applyOperatorPlatformFleetBootstrap(fleet: OperatorPlatformBootstrapFleetRequest, mode: OperatorPlatformBootstrapMode, options?: OperatorPlatformBootstrapOptions & {
199
202
  secrets?: PlatformBootstrapSecrets;
@@ -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.143";
1463
+ var VERSION = "0.1.145";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -3555,6 +3555,9 @@ function renderPlatformActivationFiles(input) {
3555
3555
  boundedInteger("greenPort", input.greenPort, 1024, 65535);
3556
3556
  if (input.bluePort === input.greenPort)
3557
3557
  throw new Error("Activation slot ports must differ.");
3558
+ if (!/^[A-Za-z0-9.-]{1,253}$/.test(input.seedMeshAddress) || input.seedMeshAddress.startsWith(".") || input.seedMeshAddress.endsWith(".")) {
3559
+ throw new Error("Activation seed mesh address is malformed.");
3560
+ }
3558
3561
  boundedInteger("keepReleases", input.keepReleases, 2, 100);
3559
3562
  const drainDeadlineMs = boundedInteger("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
3560
3563
  if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
@@ -3565,6 +3568,7 @@ function renderPlatformActivationFiles(input) {
3565
3568
  `FZ_USER=${input.serviceUser}`,
3566
3569
  `FZ_BLUE_PORT=${input.bluePort}`,
3567
3570
  `FZ_GREEN_PORT=${input.greenPort}`,
3571
+ `FZ_SEED_MESH_ADDRESS=${input.seedMeshAddress}`,
3568
3572
  `FZ_HEALTH_PATH=${input.healthPath}`,
3569
3573
  `FZ_KEEP_RELEASES=${input.keepReleases}`,
3570
3574
  `FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
@@ -4937,11 +4941,15 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4937
4941
  concurrencyLimit: runtime.environment.concurrencyLimit,
4938
4942
  workerDrainSeconds: Math.ceil(runtime.environment.drainDeadlineMs / 1000)
4939
4943
  });
4944
+ const seedMeshAddress = runtime.environment.databaseAddress ?? config.database.address;
4945
+ if (!seedMeshAddress)
4946
+ throw new Error("platform activation requires the private seed mesh address");
4940
4947
  const activation = renderPlatformActivationFiles({
4941
4948
  root: config.deployRoot ?? "/opt/forgezero",
4942
4949
  serviceUser: runtime.serviceUser,
4943
4950
  bluePort: runtime.bluePort,
4944
4951
  greenPort: runtime.greenPort,
4952
+ seedMeshAddress,
4945
4953
  healthPath: runtime.healthPath,
4946
4954
  keepReleases: runtime.keepReleases,
4947
4955
  drainDeadlineMs: runtime.environment.drainDeadlineMs
@@ -7443,7 +7451,21 @@ async function applyOperatorPlatformFleetBootstrap(fleet, mode, options = {}) {
7443
7451
  for (const config of configs.slice(1))
7444
7452
  validatePlatformBootstrapSecrets(config, secrets);
7445
7453
  }
7446
- const settled = await Promise.allSettled(requests.map((request) => applyOperatorPlatformBootstrap(request, mode, { ...options, secrets })));
7454
+ const settled = [];
7455
+ if (mode === "apply") {
7456
+ settled.push(...await Promise.allSettled(requests.map((request) => applyOperatorPlatformBootstrap(request, mode, { ...options, secrets }))));
7457
+ } else {
7458
+ for (const request of requests) {
7459
+ try {
7460
+ settled.push({
7461
+ status: "fulfilled",
7462
+ value: await applyOperatorPlatformBootstrap(request, mode, { ...options, secrets })
7463
+ });
7464
+ } catch (reason) {
7465
+ settled.push({ status: "rejected", reason });
7466
+ }
7467
+ }
7468
+ }
7447
7469
  const outcomes = settled.map((result, index) => {
7448
7470
  const config = configs[index];
7449
7471
  const address = requests[index].target.address;
@@ -176,6 +176,8 @@ export interface PlatformActivationConfig {
176
176
  serviceUser: string;
177
177
  bluePort: number;
178
178
  greenPort: number;
179
+ /** Private address used by the seed mesh; never exposed through nginx. */
180
+ seedMeshAddress: string;
179
181
  healthPath: string;
180
182
  keepReleases: number;
181
183
  drainDeadlineMs?: number;
@@ -1168,6 +1168,9 @@ function renderPlatformActivationFiles(input) {
1168
1168
  boundedInteger("greenPort", input.greenPort, 1024, 65535);
1169
1169
  if (input.bluePort === input.greenPort)
1170
1170
  throw new Error("Activation slot ports must differ.");
1171
+ if (!/^[A-Za-z0-9.-]{1,253}$/.test(input.seedMeshAddress) || input.seedMeshAddress.startsWith(".") || input.seedMeshAddress.endsWith(".")) {
1172
+ throw new Error("Activation seed mesh address is malformed.");
1173
+ }
1171
1174
  boundedInteger("keepReleases", input.keepReleases, 2, 100);
1172
1175
  const drainDeadlineMs = boundedInteger("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
1173
1176
  if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
@@ -1178,6 +1181,7 @@ function renderPlatformActivationFiles(input) {
1178
1181
  `FZ_USER=${input.serviceUser}`,
1179
1182
  `FZ_BLUE_PORT=${input.bluePort}`,
1180
1183
  `FZ_GREEN_PORT=${input.greenPort}`,
1184
+ `FZ_SEED_MESH_ADDRESS=${input.seedMeshAddress}`,
1181
1185
  `FZ_HEALTH_PATH=${input.healthPath}`,
1182
1186
  `FZ_KEEP_RELEASES=${input.keepReleases}`,
1183
1187
  `FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
@@ -1274,6 +1278,15 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
1274
1278
  }
1275
1279
  const target = previousSlot === "blue" ? "green" : "blue";
1276
1280
  const port = target === "blue" ? normalized.bluePort : normalized.greenPort;
1281
+ const meshProbe = async (slotPort, path2) => {
1282
+ try {
1283
+ const response = await request(`http://${normalized.seedMeshAddress}:${slotPort}/_internal/forgezero/seed-mesh/${path2}`, { signal: AbortSignal.timeout(2000) });
1284
+ return response.ok;
1285
+ } catch {
1286
+ return false;
1287
+ }
1288
+ };
1289
+ const previousHeldSeed = previousSlot ? await meshProbe(previousSlot === "blue" ? normalized.bluePort : normalized.greenPort, "resident") : false;
1277
1290
  const targetLink = join2(slots, target);
1278
1291
  let previousTarget;
1279
1292
  try {
@@ -1311,6 +1324,19 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
1311
1324
  await stopTarget();
1312
1325
  throw new Error("target slot failed its health deadline");
1313
1326
  }
1327
+ if (previousHeldSeed) {
1328
+ let seedReady = false;
1329
+ for (let attempt = 0;attempt < 30; attempt += 1) {
1330
+ seedReady = await meshProbe(port, "ready");
1331
+ if (seedReady)
1332
+ break;
1333
+ await sleep(1000);
1334
+ }
1335
+ if (!seedReady) {
1336
+ await stopTarget();
1337
+ throw new Error("target slot failed RAM-seed handover deadline");
1338
+ }
1339
+ }
1314
1340
  const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
1315
1341
  const backup = `${upstream}.forgezero-backup`;
1316
1342
  if (existsSync2(upstream))
@@ -3050,7 +3050,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3050
3050
  }
3051
3051
 
3052
3052
  // src/version.ts
3053
- var VERSION3 = "0.1.143";
3053
+ var VERSION3 = "0.1.145";
3054
3054
 
3055
3055
  // src/egress-policy.ts
3056
3056
  import { realpathSync as realpathSync3 } from "node:fs";
@@ -4685,6 +4685,9 @@ function renderPlatformActivationFiles(input) {
4685
4685
  boundedInteger("greenPort", input.greenPort, 1024, 65535);
4686
4686
  if (input.bluePort === input.greenPort)
4687
4687
  throw new Error("Activation slot ports must differ.");
4688
+ if (!/^[A-Za-z0-9.-]{1,253}$/.test(input.seedMeshAddress) || input.seedMeshAddress.startsWith(".") || input.seedMeshAddress.endsWith(".")) {
4689
+ throw new Error("Activation seed mesh address is malformed.");
4690
+ }
4688
4691
  boundedInteger("keepReleases", input.keepReleases, 2, 100);
4689
4692
  const drainDeadlineMs = boundedInteger("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
4690
4693
  if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
@@ -4695,6 +4698,7 @@ function renderPlatformActivationFiles(input) {
4695
4698
  `FZ_USER=${input.serviceUser}`,
4696
4699
  `FZ_BLUE_PORT=${input.bluePort}`,
4697
4700
  `FZ_GREEN_PORT=${input.greenPort}`,
4701
+ `FZ_SEED_MESH_ADDRESS=${input.seedMeshAddress}`,
4698
4702
  `FZ_HEALTH_PATH=${input.healthPath}`,
4699
4703
  `FZ_KEEP_RELEASES=${input.keepReleases}`,
4700
4704
  `FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
@@ -4791,6 +4795,15 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
4791
4795
  }
4792
4796
  const target = previousSlot === "blue" ? "green" : "blue";
4793
4797
  const port = target === "blue" ? normalized.bluePort : normalized.greenPort;
4798
+ const meshProbe = async (slotPort, path2) => {
4799
+ try {
4800
+ const response = await request(`http://${normalized.seedMeshAddress}:${slotPort}/_internal/forgezero/seed-mesh/${path2}`, { signal: AbortSignal.timeout(2000) });
4801
+ return response.ok;
4802
+ } catch {
4803
+ return false;
4804
+ }
4805
+ };
4806
+ const previousHeldSeed = previousSlot ? await meshProbe(previousSlot === "blue" ? normalized.bluePort : normalized.greenPort, "resident") : false;
4794
4807
  const targetLink = join5(slots, target);
4795
4808
  let previousTarget;
4796
4809
  try {
@@ -4828,6 +4841,19 @@ async function activatePlatformRelease(config, requestedRelease, options = {}) {
4828
4841
  await stopTarget();
4829
4842
  throw new Error("target slot failed its health deadline");
4830
4843
  }
4844
+ if (previousHeldSeed) {
4845
+ let seedReady = false;
4846
+ for (let attempt = 0;attempt < 30; attempt += 1) {
4847
+ seedReady = await meshProbe(port, "ready");
4848
+ if (seedReady)
4849
+ break;
4850
+ await sleep(1000);
4851
+ }
4852
+ if (!seedReady) {
4853
+ await stopTarget();
4854
+ throw new Error("target slot failed RAM-seed handover deadline");
4855
+ }
4856
+ }
4831
4857
  const upstream = "/etc/nginx/conf.d/forgezero-upstream.conf";
4832
4858
  const backup = `${upstream}.forgezero-backup`;
4833
4859
  if (existsSync8(upstream))
package/dist/provision.js CHANGED
@@ -3050,7 +3050,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3050
3050
  }
3051
3051
 
3052
3052
  // src/version.ts
3053
- var VERSION3 = "0.1.143";
3053
+ var VERSION3 = "0.1.145";
3054
3054
 
3055
3055
  // src/egress-policy.ts
3056
3056
  import { realpathSync as realpathSync3 } from "node:fs";
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.143";
2
+ export declare const VERSION = "0.1.145";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.143",
3
+ "version": "0.1.145",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",