@forgezero/agent 0.1.29 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/fz-agent.js CHANGED
@@ -4886,6 +4886,16 @@ import { readFileSync } from "fs";
4886
4886
  var BUN_INSTALLER_SHA256 = "bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd";
4887
4887
  var ARANGO_SHA256 = "b5a9197b4343f2ed554e1ebc1ef8e6529c7c39cde0035cdc311a4747a3355066";
4888
4888
  var CLOUDFLARED_SHA256 = "9d71c677db00134c1bd4144b7783486b654ad281b1ea62b4972098d19f770f17";
4889
+ var OS_CATALOG = [
4890
+ { id: "ubuntu", version: "26.04", architecture: "x64", status: "active" }
4891
+ ];
4892
+ var SOFTWARE_CATALOG = [
4893
+ { id: "bun", version: "1.3.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4894
+ { id: "nginx", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4895
+ { id: "arangodb", version: "3.11.14", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4896
+ { id: "cloudflared", version: "2026.7.3", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" },
4897
+ { id: "ufw", version: "ubuntu-26.04", status: "active", os: "ubuntu", osVersion: "26.04", architecture: "x64", evidence: "reviewed-strategy-and-tests" }
4898
+ ];
4889
4899
  var UBUNTU_2604_X64 = [
4890
4900
  {
4891
4901
  requirement: { id: "bun", version: "1.3.14" },
@@ -4924,7 +4934,7 @@ function observeSoftwareHost(osRelease = readFileSync("/etc/os-release", "utf8")
4924
4934
  architecture
4925
4935
  };
4926
4936
  }
4927
- function validateSoftwareRequirements(value) {
4937
+ function validateSoftwareRequirements(value, _options = {}) {
4928
4938
  if (!Array.isArray(value) || value.length > 32)
4929
4939
  throw new Error("software requirements must be an array of at most 32 entries");
4930
4940
  const seen = new Set;
@@ -4942,13 +4952,18 @@ function validateSoftwareRequirements(value) {
4942
4952
  if (seen.has(requirement.id))
4943
4953
  throw new Error(`duplicate software requirement: ${requirement.id}`);
4944
4954
  seen.add(requirement.id);
4955
+ const catalog = SOFTWARE_CATALOG.find((candidate) => candidate.id === requirement.id && candidate.version === requirement.version);
4956
+ if (!catalog || catalog.status !== "active") {
4957
+ throw new Error(`software requirement is not active: ${requirement.id}@${requirement.version}`);
4958
+ }
4945
4959
  return requirement;
4946
4960
  });
4947
4961
  }
4948
4962
  async function ensureSoftwareRequirements(requirementsInput, options) {
4949
4963
  const requirements = validateSoftwareRequirements(requirementsInput);
4950
4964
  const observation = options.observation ?? observeSoftwareHost();
4951
- if (observation.os.id !== "ubuntu" || observation.os.versionId !== "26.04" || observation.architecture !== "x64") {
4965
+ const os = OS_CATALOG.find((candidate) => candidate.id === observation.os.id && candidate.version === observation.os.versionId && candidate.architecture === observation.architecture);
4966
+ if (!os || os.status !== "active") {
4952
4967
  throw new Error(`unsupported software strategy: ${observation.os.id} ${observation.os.versionId} ${observation.architecture}`);
4953
4968
  }
4954
4969
  const results = [];
@@ -4973,7 +4988,8 @@ async function ensureSoftwareRequirements(requirementsInput, options) {
4973
4988
  }
4974
4989
 
4975
4990
  // src/definition.ts
4976
- var PIPELINE_VERSION = 1;
4991
+ var PIPELINE_VERSION = 2;
4992
+ var DEPLOY_SCHEMA_URL = "https://www.forgezero.net/schemas/deploy-v2.json";
4977
4993
 
4978
4994
  class DefinitionError extends Error {
4979
4995
  constructor(message) {
@@ -4998,6 +5014,7 @@ var exactKeys = (value, allowed, where) => {
4998
5014
  if (unknown.length > 0)
4999
5015
  throw new DefinitionError(`${where} contains unknown field(s): ${unknown.join(", ")}.`);
5000
5016
  };
5017
+ var NAME = /^[a-z][a-z0-9-]{0,62}$/;
5001
5018
  var RESERVED_STEP_ENV = new Set([
5002
5019
  "PATH",
5003
5020
  "HOME",
@@ -5010,46 +5027,67 @@ var RESERVED_STEP_ENV = new Set([
5010
5027
  "GIT_SSH",
5011
5028
  "GIT_SSH_COMMAND"
5012
5029
  ]);
5013
- function parseDeployDefinition(value) {
5030
+ function parseDeployDefinition(value, options = {}) {
5014
5031
  const root = record(value, "pipeline");
5015
- exactKeys(root, ["version", "name", "requireAttestation", "roles", "steps"], "pipeline");
5032
+ exactKeys(root, ["$schema", "version", "name", "requireAttestation", "profiles", "steps"], "pipeline");
5033
+ if (root.$schema !== undefined && root.$schema !== DEPLOY_SCHEMA_URL) {
5034
+ throw new DefinitionError(`pipeline.$schema must be ${DEPLOY_SCHEMA_URL}.`);
5035
+ }
5016
5036
  if (root.version !== PIPELINE_VERSION) {
5017
5037
  throw new DefinitionError(`pipeline.version must be ${PIPELINE_VERSION}.`);
5018
5038
  }
5019
- if (!Array.isArray(root.roles) || root.roles.length === 0) {
5020
- throw new DefinitionError("pipeline.roles must contain at least one role.");
5039
+ if (root.requireAttestation !== undefined && typeof root.requireAttestation !== "boolean") {
5040
+ throw new DefinitionError("pipeline.requireAttestation must be a boolean.");
5041
+ }
5042
+ const rawProfiles = record(root.profiles, "pipeline.profiles");
5043
+ const profileEntries = Object.entries(rawProfiles);
5044
+ if (profileEntries.length === 0 || profileEntries.length > 32) {
5045
+ throw new DefinitionError("pipeline.profiles must contain from 1 to 32 named profiles.");
5021
5046
  }
5022
5047
  if (!Array.isArray(root.steps) || root.steps.length === 0) {
5023
5048
  throw new DefinitionError("pipeline.steps must contain at least one step.");
5024
5049
  }
5025
- const roles = root.roles.map((raw, index) => {
5026
- const role = record(raw, `roles[${index}]`);
5027
- exactKeys(role, ["name", "software"], `roles[${index}]`);
5028
- if (!Array.isArray(role.software)) {
5029
- throw new DefinitionError(`roles[${index}].software must be an array.`);
5050
+ const profiles = {};
5051
+ for (const [name2, raw] of profileEntries) {
5052
+ if (!NAME.test(name2))
5053
+ throw new DefinitionError(`pipeline profile name is invalid: ${name2}.`);
5054
+ const profile = record(raw, `profiles.${name2}`);
5055
+ exactKeys(profile, ["software"], `profiles.${name2}`);
5056
+ if (!Array.isArray(profile.software)) {
5057
+ throw new DefinitionError(`profiles.${name2}.software must be an array.`);
5030
5058
  }
5031
- return {
5032
- name: text(role.name, `roles[${index}].name`),
5033
- software: validateSoftwareRequirements(role.software)
5034
- };
5035
- });
5036
- if (new Set(roles.map((role) => role.name)).size !== roles.length) {
5037
- throw new DefinitionError("pipeline.roles must have unique names.");
5059
+ profiles[name2] = { software: validateSoftwareRequirements(profile.software, options) };
5038
5060
  }
5039
5061
  const phases = new Set(["build", "release", "migrate", "health"]);
5040
5062
  const steps = root.steps.map((raw, index) => {
5041
5063
  const step = record(raw, `steps[${index}]`);
5042
- exactKeys(step, ["name", "run", "phase", "secrets", "once", "always", "timeoutMs", "when"], `steps[${index}]`);
5064
+ exactKeys(step, ["name", "run", "phase", "scope", "profiles", "secrets", "always", "timeoutMs", "when"], `steps[${index}]`);
5043
5065
  const phase = text(step.phase, `steps[${index}].phase`);
5044
5066
  if (!phases.has(phase))
5045
5067
  throw new DefinitionError(`steps[${index}].phase is not supported.`);
5046
- if (step.secrets !== undefined && (!Array.isArray(step.secrets) || step.secrets.some((name) => typeof name !== "string" || !/^[A-Z_][A-Z0-9_]*$/.test(name)))) {
5068
+ if (step.scope !== "target" && step.scope !== "release") {
5069
+ throw new DefinitionError(`steps[${index}].scope must be target or release.`);
5070
+ }
5071
+ if (step.always !== undefined && typeof step.always !== "boolean") {
5072
+ throw new DefinitionError(`steps[${index}].always must be a boolean.`);
5073
+ }
5074
+ let selectedProfiles;
5075
+ if (step.profiles !== undefined) {
5076
+ if (!Array.isArray(step.profiles) || step.profiles.length === 0 || step.profiles.some((name2) => typeof name2 !== "string" || !Object.hasOwn(profiles, name2))) {
5077
+ throw new DefinitionError(`steps[${index}].profiles must name existing profiles.`);
5078
+ }
5079
+ selectedProfiles = [...step.profiles];
5080
+ if (new Set(selectedProfiles).size !== selectedProfiles.length) {
5081
+ throw new DefinitionError(`steps[${index}].profiles must not contain duplicates.`);
5082
+ }
5083
+ }
5084
+ if (step.secrets !== undefined && (!Array.isArray(step.secrets) || step.secrets.some((name2) => typeof name2 !== "string" || !/^[A-Z_][A-Z0-9_]*$/.test(name2)))) {
5047
5085
  throw new DefinitionError(`steps[${index}].secrets must contain names only.`);
5048
5086
  }
5049
5087
  if (Array.isArray(step.secrets) && new Set(step.secrets).size !== step.secrets.length) {
5050
5088
  throw new DefinitionError(`steps[${index}].secrets must not contain duplicates.`);
5051
5089
  }
5052
- if (Array.isArray(step.secrets) && step.secrets.some((name) => RESERVED_STEP_ENV.has(String(name)))) {
5090
+ if (Array.isArray(step.secrets) && step.secrets.some((name2) => RESERVED_STEP_ENV.has(String(name2)))) {
5053
5091
  throw new DefinitionError(`steps[${index}].secrets may not replace process-control environment variables.`);
5054
5092
  }
5055
5093
  const timeoutMs = step.timeoutMs === undefined ? undefined : Number(step.timeoutMs);
@@ -5060,11 +5098,11 @@ function parseDeployDefinition(value) {
5060
5098
  if (step.when !== undefined) {
5061
5099
  const conditions = record(step.when, `steps[${index}].when`);
5062
5100
  when = {};
5063
- for (const [name, expected] of Object.entries(conditions)) {
5064
- if (!/^[A-Z_][A-Z0-9_]*$/.test(name) || typeof expected !== "string" || expected.length === 0) {
5101
+ for (const [name2, expected] of Object.entries(conditions)) {
5102
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(name2) || typeof expected !== "string" || expected.length === 0) {
5065
5103
  throw new DefinitionError(`steps[${index}].when must map environment names to non-empty strings.`);
5066
5104
  }
5067
- when[name] = expected;
5105
+ when[name2] = expected;
5068
5106
  }
5069
5107
  if (Object.keys(when).length === 0)
5070
5108
  throw new DefinitionError(`steps[${index}].when must not be empty.`);
@@ -5073,8 +5111,9 @@ function parseDeployDefinition(value) {
5073
5111
  name: text(step.name, `steps[${index}].name`),
5074
5112
  run: text(step.run, `steps[${index}].run`),
5075
5113
  phase,
5114
+ scope: step.scope,
5115
+ profiles: selectedProfiles,
5076
5116
  secrets: step.secrets,
5077
- once: step.once === true,
5078
5117
  always: step.always === true,
5079
5118
  timeoutMs,
5080
5119
  when
@@ -5083,15 +5122,32 @@ function parseDeployDefinition(value) {
5083
5122
  if (new Set(steps.map((step) => step.name)).size !== steps.length) {
5084
5123
  throw new DefinitionError("pipeline.steps must have unique names.");
5085
5124
  }
5125
+ const name = text(root.name, "pipeline.name");
5126
+ if (name.length > 120)
5127
+ throw new DefinitionError("pipeline.name must be at most 120 characters.");
5086
5128
  return {
5087
5129
  version: PIPELINE_VERSION,
5088
- name: text(root.name, "pipeline.name"),
5130
+ name,
5089
5131
  requireAttestation: root.requireAttestation === true,
5090
- roles,
5132
+ profiles,
5091
5133
  steps
5092
5134
  };
5093
5135
  }
5094
5136
 
5137
+ // src/deploy-file.ts
5138
+ import { createHash } from "crypto";
5139
+ var stable = (value) => {
5140
+ if (Array.isArray(value))
5141
+ return `[${value.map(stable).join(",")}]`;
5142
+ if (value && typeof value === "object") {
5143
+ return `{${Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stable(entry)}`).join(",")}}`;
5144
+ }
5145
+ return JSON.stringify(value);
5146
+ };
5147
+ function deployDefinitionDigest(definition) {
5148
+ return `sha256:${createHash("sha256").update(stable(definition)).digest("hex")}`;
5149
+ }
5150
+
5095
5151
  // src/pipeline.ts
5096
5152
  class PipelineError extends Error {
5097
5153
  code;
@@ -5217,7 +5273,7 @@ function createDeploymentManager(options) {
5217
5273
  const exec = options.exec ?? shell;
5218
5274
  const projectExec = options.projectExec ?? exec;
5219
5275
  const now = options.now ?? Date.now;
5220
- const readDefinition = options.readDefinition ?? ((path) => Bun.YAML.parse(readFileSync2(path, "utf8")));
5276
+ const readDefinition = options.readDefinition ?? ((path) => JSON.parse(readFileSync2(path, "utf8")));
5221
5277
  const credentialsDirectory = process.env.CREDENTIALS_DIRECTORY;
5222
5278
  const gitCredentialPath = options.gitCredentialPath ?? (credentialsDirectory ? join(credentialsDirectory, "git-deploy-key") : undefined);
5223
5279
  const knownHostsPath = options.knownHostsPath ?? "/etc/forgezero/git/known_hosts";
@@ -5332,22 +5388,23 @@ function createDeploymentManager(options) {
5332
5388
  if (request.revision && head !== request.revision) {
5333
5389
  throw new DeploymentError("SOURCE_FAILED", `Git checked out ${head}, not requested ${request.revision}.`);
5334
5390
  }
5335
- const definition = parseDeployDefinition(readDefinition(join(release, ".fz", "deploy.yaml")));
5336
- const selectedRole = definition.roles.find((candidate) => candidate.name === options.role);
5337
- if (!selectedRole)
5338
- throw new DeploymentError("PIPELINE_FAILED", `pipeline role does not exist: ${options.role}.`);
5339
- if (selectedRole.software.length > 0) {
5391
+ const definition = parseDeployDefinition(readDefinition(join(release, ".fz", "deploy.json")));
5392
+ const definitionDigest = deployDefinitionDigest(definition);
5393
+ const selectedProfile = definition.profiles[options.profile];
5394
+ if (!selectedProfile)
5395
+ throw new DeploymentError("PIPELINE_FAILED", `pipeline profile does not exist: ${options.profile}.`);
5396
+ if (selectedProfile.software.length > 0) {
5340
5397
  if (!options.ensureSoftware) {
5341
5398
  throw new DeploymentError("PIPELINE_FAILED", "the supervised software helper is unavailable");
5342
5399
  }
5343
- await options.ensureSoftware(selectedRole.software);
5400
+ await options.ensureSoftware(selectedProfile.software);
5344
5401
  }
5345
5402
  const phaseEnvironment = {
5346
5403
  ...options.environment ?? {},
5347
5404
  FZ_RELEASE: release,
5348
5405
  FZ_DEPLOY_REVISION: head,
5349
5406
  FZ_DEPLOY_BRANCH: options.branch,
5350
- FZ_DEPLOY_ROLE: options.role,
5407
+ FZ_DEPLOY_PROFILE: options.profile,
5351
5408
  ...options.publicApiUrl ? { PUBLIC_API_URL: options.publicApiUrl } : {}
5352
5409
  };
5353
5410
  const phaseExec = ({ command, env: secrets, timeoutMs }) => projectExec({ command, cwd: release, env: { ...phaseEnvironment, ...secrets }, timeoutMs });
@@ -5363,7 +5420,7 @@ function createDeploymentManager(options) {
5363
5420
  return {
5364
5421
  name: `${definition.name}:${phase}`,
5365
5422
  requireAttestation: definition.requireAttestation,
5366
- steps: definition.steps.filter((step) => step.phase === phase && (!step.once || request.coordinator === true) && (!step.when || Object.entries(step.when).every(([name, expected]) => phaseEnvironment[name] === expected)))
5423
+ steps: definition.steps.filter((step) => step.phase === phase && (!step.profiles || step.profiles.includes(options.profile)) && (step.scope === "target" || request.releaseExecutor === true) && (!step.when || Object.entries(step.when).every(([name, expected]) => phaseEnvironment[name] === expected)))
5367
5424
  };
5368
5425
  })
5369
5426
  ].filter((pipeline) => pipeline.steps.length > 0);
@@ -5382,6 +5439,9 @@ function createDeploymentManager(options) {
5382
5439
  repository: options.repository,
5383
5440
  branch: options.branch,
5384
5441
  revision: head,
5442
+ definitionDigest,
5443
+ definitionVersion: definition.version,
5444
+ profile: options.profile,
5385
5445
  release,
5386
5446
  ok: true,
5387
5447
  phases
@@ -5400,7 +5460,7 @@ function createDeploymentManager(options) {
5400
5460
  return revision.toLowerCase();
5401
5461
  },
5402
5462
  deploy(request = {}) {
5403
- const revisionKey = request.revision ? `${request.revision.toLowerCase()}:${request.coordinator === true ? "coordinator" : "node"}` : undefined;
5463
+ const revisionKey = request.revision ? `${request.revision.toLowerCase()}:${request.releaseExecutor === true ? "release" : "target"}` : undefined;
5404
5464
  if (revisionKey) {
5405
5465
  const existing = activeRevisions.get(revisionKey);
5406
5466
  if (existing)
@@ -5592,7 +5652,7 @@ async function deployClaim(options, claim) {
5592
5652
  schedule();
5593
5653
  let result;
5594
5654
  try {
5595
- result = await manager.deploy({ revision: claim.revision, coordinator: claim.coordinator }).result;
5655
+ result = await manager.deploy({ revision: claim.revision, releaseExecutor: claim.releaseExecutor }).result;
5596
5656
  } finally {
5597
5657
  stopped = true;
5598
5658
  clearTimer(timer);
@@ -5645,7 +5705,10 @@ async function pullDeploymentOnce(options) {
5645
5705
  claimToken: claim.claimToken,
5646
5706
  ok: true,
5647
5707
  detail: `Deployed ${result.revision}.`,
5648
- release: result.release
5708
+ release: result.release,
5709
+ definitionDigest: result.definitionDigest,
5710
+ definitionVersion: result.definitionVersion,
5711
+ profile: result.profile
5649
5712
  });
5650
5713
  return { status: "deployed", claim, result };
5651
5714
  }
@@ -6064,7 +6127,7 @@ import { chmodSync as chmodSync5, existsSync as existsSync6, unlinkSync as unlin
6064
6127
  import { connect as connect2, createServer as createServer3 } from "net";
6065
6128
 
6066
6129
  // src/metal-provision.ts
6067
- import { createHash } from "crypto";
6130
+ import { createHash as createHash2 } from "crypto";
6068
6131
  import {
6069
6132
  existsSync as existsSync5,
6070
6133
  mkdirSync as mkdirSync3,
@@ -6217,8 +6280,8 @@ function membersOfLinuxList(value, label) {
6217
6280
  throw new MetalProvisionError(`${label} list overlaps itself`);
6218
6281
  return members;
6219
6282
  }
6220
- var guestNameFor = (computeKey) => `fzg-${createHash("sha256").update(computeKey).digest("hex").slice(0, 16)}`;
6221
- var tapNameFor = (computeKey) => `fzt${createHash("sha256").update(computeKey).digest("hex").slice(0, 12)}`;
6283
+ var guestNameFor = (computeKey) => `fzg-${createHash2("sha256").update(computeKey).digest("hex").slice(0, 16)}`;
6284
+ var tapNameFor = (computeKey) => `fzt${createHash2("sha256").update(computeKey).digest("hex").slice(0, 12)}`;
6222
6285
  var macForAddress = (address) => {
6223
6286
  const octets = address.split(".").map(Number);
6224
6287
  if (octets.length !== 4 || octets.some((value) => !Number.isInteger(value) || value < 0 || value > 255)) {
@@ -6300,7 +6363,7 @@ function allocateAddress(profile, computeKey, rows) {
6300
6363
  return existing.address;
6301
6364
  const used = new Set(rows.map((row) => row.address));
6302
6365
  const width = profile.addressEnd - profile.addressStart + 1;
6303
- const start = createHash("sha256").update(computeKey).digest().readUInt16BE(0) % width;
6366
+ const start = createHash2("sha256").update(computeKey).digest().readUInt16BE(0) % width;
6304
6367
  for (let offset = 0;offset < width; offset += 1) {
6305
6368
  const last = profile.addressStart + (start + offset) % width;
6306
6369
  const address = `${profile.subnetPrefix}.${last}`;
@@ -7515,7 +7578,7 @@ function materializeWarpMdm(options) {
7515
7578
  }
7516
7579
 
7517
7580
  // src/agent-update.ts
7518
- import { createHash as createHash2, timingSafeEqual, randomUUID as randomUUID2 } from "crypto";
7581
+ import { createHash as createHash3, timingSafeEqual, randomUUID as randomUUID2 } from "crypto";
7519
7582
  import {
7520
7583
  chmodSync as chmodSync9,
7521
7584
  existsSync as existsSync10,
@@ -7633,7 +7696,7 @@ async function stageAgentRelease(releaseInput, options) {
7633
7696
  throw new Error("agent update tarball is empty or exceeds the size limit");
7634
7697
  }
7635
7698
  const expected = Buffer.from(release.integrity.slice("sha512-".length), "base64");
7636
- const actual = createHash2("sha512").update(bytes).digest();
7699
+ const actual = createHash3("sha512").update(bytes).digest();
7637
7700
  if (!timingSafeEqual(actual, expected))
7638
7701
  throw new Error("agent update integrity mismatch");
7639
7702
  writeFileSync6(archive, bytes, { mode: 384, flag: "wx" });
@@ -7875,7 +7938,7 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
7875
7938
  import { readFileSync as readFileSync7 } from "fs";
7876
7939
 
7877
7940
  // src/version.ts
7878
- var VERSION2 = "0.1.29";
7941
+ var VERSION2 = "0.1.31";
7879
7942
 
7880
7943
  // src/agent-heartbeat.ts
7881
7944
  var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
@@ -8164,7 +8227,7 @@ if (import.meta.main) {
8164
8227
  " FZ_SEED_PATH legacy/dev seed file (default: " + DEFAULT_SEED_PATH + ")",
8165
8228
  " FZ_NODE_KEY override the node key (default: derived from the seed)",
8166
8229
  "",
8167
- " deploy [--revision=<full-sha>] [--coordinator]",
8230
+ " deploy [--revision=<full-sha>] [--release-executor]",
8168
8231
  " identity print this sealed seed's public identity",
8169
8232
  " enrol consume a systemd-loaded compute capability",
8170
8233
  " metal-helper --profile=/etc/forgezero/metal.json",
@@ -8221,9 +8284,9 @@ if (import.meta.main) {
8221
8284
  const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
8222
8285
  if (!profilePath)
8223
8286
  throw new Error("metal-helper requires --profile=/absolute/path.json");
8224
- const profile = JSON.parse(readFileSync8(profilePath, "utf8"));
8287
+ const profile2 = JSON.parse(readFileSync8(profilePath, "utf8"));
8225
8288
  const helper = startMetalHelper({
8226
- profile,
8289
+ profile: profile2,
8227
8290
  socketPath: process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET
8228
8291
  });
8229
8292
  console.log(`[metal-helper] listening on ${process.env.FZ_METAL_HELPER_SOCKET ?? DEFAULT_METAL_HELPER_SOCKET}`);
@@ -8339,8 +8402,8 @@ if (import.meta.main) {
8339
8402
  const profilePath = args.find((arg) => arg.startsWith("--profile="))?.slice("--profile=".length);
8340
8403
  if (!profilePath)
8341
8404
  throw new Error("metal-isolation requires --profile=/absolute/path.json");
8342
- const profile = JSON.parse(readFileSync8(profilePath, "utf8"));
8343
- await applyMetalIsolation(profile);
8405
+ const profile2 = JSON.parse(readFileSync8(profilePath, "utf8"));
8406
+ await applyMetalIsolation(profile2);
8344
8407
  console.log("[metal-isolation] host and guest cgroup boundaries active");
8345
8408
  process.exit(0);
8346
8409
  }
@@ -8468,7 +8531,7 @@ if (import.meta.main) {
8468
8531
  op: "deploy",
8469
8532
  request: {
8470
8533
  revision: revisionArg?.slice("--revision=".length),
8471
- coordinator: args.includes("--coordinator")
8534
+ releaseExecutor: args.includes("--release-executor")
8472
8535
  }
8473
8536
  } : command2 === "cancel" ? { op: "cancel", id: idArg?.slice("--id=".length) ?? "" } : ["pause-key", "resume-key", "stop-key", "start-key"].includes(command2) ? {
8474
8537
  op: command2,
@@ -8575,19 +8638,19 @@ if (import.meta.main) {
8575
8638
  } : systemdDeploymentSecrets;
8576
8639
  const repository = process.env.FZ_DEPLOY_REPO;
8577
8640
  const branch = process.env.FZ_DEPLOY_BRANCH;
8578
- const role = process.env.FZ_DEPLOY_ROLE;
8641
+ const profile = process.env.FZ_DEPLOY_PROFILE;
8579
8642
  const root = process.env.FZ_DEPLOY_ROOT;
8580
8643
  const pullEnabled = process.env.FZ_DEPLOY_PULL === "true" && Boolean(process.env.FZ_API);
8581
8644
  if (pullEnabled && !binding) {
8582
8645
  throw new Error("agent: outbound guest deployment requires a persisted tenant enrolment binding");
8583
8646
  }
8584
- if (root && (repository && branch && role || pullEnabled)) {
8647
+ if (root && (repository && branch && profile || pullEnabled)) {
8585
8648
  const managers = new Map;
8586
8649
  const managerOptions = (source, key) => ({
8587
8650
  key,
8588
8651
  repository: source.repository,
8589
8652
  branch: source.branch,
8590
- role: source.role,
8653
+ profile: source.profile,
8591
8654
  sourceAuth: source.auth,
8592
8655
  root,
8593
8656
  publicApiUrl: process.env.FZ_PUBLIC_API_URL,
@@ -8604,7 +8667,7 @@ if (import.meta.main) {
8604
8667
  ensureSoftware: (requirements) => requestSoftware(requirements, process.env.FZ_SOFTWARE_HELPER_SOCKET ?? DEFAULT_SOFTWARE_HELPER_SOCKET),
8605
8668
  projectExec: process.env.FZ_DEPLOY_RUNNER_SOCKET ? (input) => requestDeploymentCommand(input, process.env.FZ_DEPLOY_RUNNER_SOCKET) : undefined
8606
8669
  });
8607
- const staticManager = repository && branch && role ? createDeploymentManager(managerOptions({ repository, branch, role }, process.env.FZ_DEPLOY_KEY ?? `${repository}:${branch}:${role}`)) : undefined;
8670
+ const staticManager = repository && branch && profile ? createDeploymentManager(managerOptions({ repository, branch, profile }, process.env.FZ_DEPLOY_KEY ?? `${repository}:${branch}:${profile}`)) : undefined;
8608
8671
  if (staticManager)
8609
8672
  managers.set("__static__", staticManager);
8610
8673
  const control = staticManager ? startControlServer(staticManager, process.env.FZ_CONTROL_SOCKET ?? DEFAULT_CONTROL_SOCKET) : undefined;
@@ -8620,7 +8683,7 @@ if (import.meta.main) {
8620
8683
  claim.pipelineKey,
8621
8684
  claim.source.repository,
8622
8685
  claim.source.branch,
8623
- claim.source.role,
8686
+ claim.source.profile,
8624
8687
  JSON.stringify(claim.source.auth ?? null)
8625
8688
  ].join("\x00");
8626
8689
  const existing = managers.get(cacheKey);
@@ -8818,6 +8881,8 @@ export {
8818
8881
  SUPPORTED_GUEST_IMAGE,
8819
8882
  SOFTWARE_HELPER_UNIT_PATH,
8820
8883
  SOFTWARE_HELPER_GROUP,
8884
+ SOFTWARE_CATALOG,
8885
+ OS_CATALOG,
8821
8886
  MAX_AGENT_TARBALL_BYTES,
8822
8887
  DeploymentError,
8823
8888
  DEFAULT_SOFTWARE_HELPER_SOCKET,