@forgezero/agent 0.1.63 → 0.1.64

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
@@ -281,14 +281,15 @@ import type { AgentUpdateOutcome, AgentUpdateReceipt, AgentUpdateRequest, AgentU
281
281
  <a id="forgezero-agent-bootstrap"></a>
282
282
  ## @forgezero/agent/bootstrap
283
283
 
284
- Typed one-time platform bootstrap and internal API-driven enrolled-compute activation contracts. Named value imports: BOOTSTRAP_STATE_PATH, PLATFORM_BOOTSTRAP_PROFILES, applyBootstrap, bootstrapIdentityDigest, bootstrapStatus, localBootstrapHost, planBootstrap, readBootstrapConfig, resolveInstalledBootstrapKind, validateBootstrapConfig, validateEnrolledComputeBootstrapSecrets, validatePlatformBootstrapSecrets. Named type imports: BootstrapConfig, BootstrapEnvironment, BootstrapHost, BootstrapPlan, BootstrapResult, BootstrapSecrets, BootstrapStatus, BootstrapStep, DatabaseAgencyParticipation, DatabaseBootstrapRole, EnrolledComputeActivationConfig, EnrolledComputeBootstrapSecrets, InstalledBootstrapKind, PlatformBootstrapConfig, PlatformBootstrapProfile, PlatformBootstrapSecrets, PlatformEnrolmentSource. Import only the names used by this file.
284
+ Typed one-time platform bootstrap and internal API-driven enrolled-compute activation contracts. Named value imports: BOOTSTRAP_STATE_PATH, PLATFORM_BOOTSTRAP_PROFILES, applyBootstrap, bootstrapIdentityDigest, bootstrapStatus, localBootstrapHost, planBootstrap, planBootstrapAgentInstall, readBootstrapConfig, resolveInstalledBootstrapKind, validateBootstrapConfig, validateEnrolledComputeBootstrapSecrets, validatePlatformBootstrapSecrets. Named type imports: BootstrapAgentInstallPhase, BootstrapConfig, BootstrapEnvironment, BootstrapHost, BootstrapPlan, BootstrapResult, BootstrapSecrets, BootstrapStatus, BootstrapStep, DatabaseAgencyParticipation, DatabaseBootstrapRole, EnrolledComputeActivationConfig, EnrolledComputeBootstrapSecrets, InstalledBootstrapKind, PlatformBootstrapConfig, PlatformBootstrapProfile, PlatformBootstrapSecrets, PlatformEnrolmentSource. Import only the names used by this file.
285
285
 
286
286
  ```text
287
287
  import { BOOTSTRAP_STATE_PATH, PLATFORM_BOOTSTRAP_PROFILES, applyBootstrap, bootstrapIdentityDigest, bootstrapStatus, localBootstrapHost } from '@forgezero/agent/bootstrap';
288
- import { planBootstrap, readBootstrapConfig, resolveInstalledBootstrapKind, validateBootstrapConfig, validateEnrolledComputeBootstrapSecrets, validatePlatformBootstrapSecrets } from '@forgezero/agent/bootstrap';
289
- import type { BootstrapConfig, BootstrapEnvironment, BootstrapHost, BootstrapPlan, BootstrapResult, BootstrapSecrets } from '@forgezero/agent/bootstrap';
290
- import type { BootstrapStatus, BootstrapStep, DatabaseAgencyParticipation, DatabaseBootstrapRole, EnrolledComputeActivationConfig, EnrolledComputeBootstrapSecrets } from '@forgezero/agent/bootstrap';
291
- import type { InstalledBootstrapKind, PlatformBootstrapConfig, PlatformBootstrapProfile, PlatformBootstrapSecrets, PlatformEnrolmentSource } from '@forgezero/agent/bootstrap';
288
+ import { planBootstrap, planBootstrapAgentInstall, readBootstrapConfig, resolveInstalledBootstrapKind, validateBootstrapConfig, validateEnrolledComputeBootstrapSecrets } from '@forgezero/agent/bootstrap';
289
+ import { validatePlatformBootstrapSecrets } from '@forgezero/agent/bootstrap';
290
+ import type { BootstrapAgentInstallPhase, BootstrapConfig, BootstrapEnvironment, BootstrapHost, BootstrapPlan, BootstrapResult } from '@forgezero/agent/bootstrap';
291
+ import type { BootstrapSecrets, BootstrapStatus, BootstrapStep, DatabaseAgencyParticipation, DatabaseBootstrapRole, EnrolledComputeActivationConfig } from '@forgezero/agent/bootstrap';
292
+ import type { EnrolledComputeBootstrapSecrets, InstalledBootstrapKind, PlatformBootstrapConfig, PlatformBootstrapProfile, PlatformBootstrapSecrets, PlatformEnrolmentSource } from '@forgezero/agent/bootstrap';
292
293
  ```
293
294
 
294
295
  <a id="forgezero-agent-bootstrap-bundle"></a>
@@ -319,12 +320,15 @@ await readBootstrapBundle(built.bundlePath, built.manifestPath);
319
320
  <a id="forgezero-agent-operator-bootstrap"></a>
320
321
  ## @forgezero/agent/operator-bootstrap
321
322
 
322
- Pinned-host operator transport that stages owner-only handoffs and invokes the same typed platform bootstrap through the caller-approved SSH agent. Named value imports: applyOperatorMetalBootstrap, applyOperatorPlatformBootstrap, planOperatorMetalBootstrap, planOperatorPlatformBootstrap, readOperatorMetalBootstrapRequest, readOperatorPlatformBootstrapRequest. Named type imports: OperatorCommand, OperatorCommandResult, OperatorMetalBootstrapMode, OperatorMetalBootstrapPlan, OperatorMetalBootstrapRequest, OperatorPlatformBootstrapMode, OperatorPlatformBootstrapOptions, OperatorPlatformBootstrapPlan, OperatorPlatformBootstrapRequest, OperatorSshHop. Import only the names used by this file.
323
+ Pinned-host operator transport that stages owner-only handoffs and invokes the same typed platform bootstrap through the caller-approved SSH agent. Named value imports: applyOperatorMetalBootstrap, applyOperatorPlatformBootstrap, applyOperatorPlatformFleetBootstrap, createOperatorSshHop, planOperatorMetalBootstrap, planOperatorPlatformBootstrap, planOperatorPlatformFleetBootstrap, readOperatorMetalBootstrapRequest, readOperatorPlatformBootstrapFleetRequest, readOperatorPlatformBootstrapRequest, writeOperatorMetalBootstrapRequest, writeOperatorPlatformBootstrapFleetRequest, writeOperatorPlatformBootstrapRequest. Named type imports: OperatorCommand, OperatorCommandResult, OperatorMetalBootstrapMode, OperatorMetalBootstrapPlan, OperatorMetalBootstrapRequest, OperatorPlatformBootstrapFleetOutcome, OperatorPlatformBootstrapFleetPlan, OperatorPlatformBootstrapFleetRequest, OperatorPlatformBootstrapFleetResult, OperatorPlatformBootstrapMode, OperatorPlatformBootstrapOptions, OperatorPlatformBootstrapPlan, OperatorPlatformBootstrapRequest, OperatorSshHop. Import only the names used by this file.
323
324
 
324
325
  ```text
325
- import { applyOperatorMetalBootstrap, applyOperatorPlatformBootstrap, planOperatorMetalBootstrap, planOperatorPlatformBootstrap, readOperatorMetalBootstrapRequest, readOperatorPlatformBootstrapRequest } from '@forgezero/agent/operator-bootstrap';
326
- import type { OperatorCommand, OperatorCommandResult, OperatorMetalBootstrapMode, OperatorMetalBootstrapPlan, OperatorMetalBootstrapRequest, OperatorPlatformBootstrapMode } from '@forgezero/agent/operator-bootstrap';
327
- import type { OperatorPlatformBootstrapOptions, OperatorPlatformBootstrapPlan, OperatorPlatformBootstrapRequest, OperatorSshHop } from '@forgezero/agent/operator-bootstrap';
326
+ import { applyOperatorMetalBootstrap, applyOperatorPlatformBootstrap, applyOperatorPlatformFleetBootstrap, createOperatorSshHop, planOperatorMetalBootstrap, planOperatorPlatformBootstrap } from '@forgezero/agent/operator-bootstrap';
327
+ import { planOperatorPlatformFleetBootstrap, readOperatorMetalBootstrapRequest, readOperatorPlatformBootstrapFleetRequest, readOperatorPlatformBootstrapRequest, writeOperatorMetalBootstrapRequest, writeOperatorPlatformBootstrapFleetRequest } from '@forgezero/agent/operator-bootstrap';
328
+ import { writeOperatorPlatformBootstrapRequest } from '@forgezero/agent/operator-bootstrap';
329
+ import type { OperatorCommand, OperatorCommandResult, OperatorMetalBootstrapMode, OperatorMetalBootstrapPlan, OperatorMetalBootstrapRequest, OperatorPlatformBootstrapFleetOutcome } from '@forgezero/agent/operator-bootstrap';
330
+ import type { OperatorPlatformBootstrapFleetPlan, OperatorPlatformBootstrapFleetRequest, OperatorPlatformBootstrapFleetResult, OperatorPlatformBootstrapMode, OperatorPlatformBootstrapOptions, OperatorPlatformBootstrapPlan } from '@forgezero/agent/operator-bootstrap';
331
+ import type { OperatorPlatformBootstrapRequest, OperatorSshHop } from '@forgezero/agent/operator-bootstrap';
328
332
  ```
329
333
 
330
334
  ## @forgezero/agent/operator-bootstrap — Run the same typed platform bootstrap from an operator laptop
@@ -749,7 +749,7 @@ async function postSignedNode(options, path, body) {
749
749
  }
750
750
 
751
751
  // src/version.ts
752
- var VERSION3 = "0.1.63";
752
+ var VERSION3 = "0.1.64";
753
753
 
754
754
  // src/agent-heartbeat.ts
755
755
  var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
@@ -1,6 +1,8 @@
1
1
  import { type ProvisionPlan } from './cli/agent-install';
2
+ import type { Capabilities } from './provision';
2
3
  import { type SoftwareCommandResult, type SoftwareRequirement } from './software';
3
4
  import { type PlatformSharedEnvironment } from './platform-bootstrap-runtime';
5
+ import { type BootstrapBundleManifest } from './bootstrap-bundle';
4
6
  export declare const PLATFORM_BOOTSTRAP_PROFILES: readonly ["platform-db-api", "platform-api"];
5
7
  export type PlatformBootstrapProfile = (typeof PLATFORM_BOOTSTRAP_PROFILES)[number];
6
8
  export type DatabaseBootstrapRole = 'master' | 'joiner' | 'none';
@@ -149,6 +151,7 @@ export interface BootstrapStatus {
149
151
  problems: string[];
150
152
  }
151
153
  export type InstalledBootstrapKind = 'compute' | 'metal';
154
+ export type BootstrapAgentInstallPhase = 'bootstrap' | 'enrol' | 'bound';
152
155
  /** One host has one bootstrap authority. Dual state is never resolved by precedence. */
153
156
  export declare function resolveInstalledBootstrapKind(states: {
154
157
  compute: boolean;
@@ -174,7 +177,7 @@ export interface BootstrapHost {
174
177
  }): Promise<SoftwareCommandResult>;
175
178
  sleep?(milliseconds: number): Promise<void>;
176
179
  ensureSoftware(requirements: readonly SoftwareRequirement[]): Promise<unknown>;
177
- installAgent(config: BootstrapConfig): Promise<ProvisionPlan>;
180
+ installAgent(config: BootstrapConfig, phase: BootstrapAgentInstallPhase): Promise<ProvisionPlan>;
178
181
  }
179
182
  export declare const BOOTSTRAP_STATE_PATH = "/var/lib/forgezero/bootstrap.json";
180
183
  export declare function validateBootstrapConfig(value: BootstrapConfig): BootstrapConfig;
@@ -185,4 +188,17 @@ export declare function applyBootstrap(input: BootstrapConfig, host?: BootstrapH
185
188
  fetcher?: typeof fetch;
186
189
  }): Promise<BootstrapResult>;
187
190
  export declare function readBootstrapConfig(path: string): BootstrapConfig;
191
+ interface BootstrapAgentInitialBundle {
192
+ path: string;
193
+ manifestPath: string;
194
+ manifest: BootstrapBundleManifest;
195
+ }
196
+ /** Compose one real local Agent convergence without consulting mutable globals. */
197
+ export declare function planBootstrapAgentInstall(config: BootstrapConfig, phase: BootstrapAgentInstallPhase, context: {
198
+ capabilities: Capabilities;
199
+ hasBinding: boolean;
200
+ hasEnrolCredential: boolean;
201
+ initialBundle?: BootstrapAgentInitialBundle;
202
+ }): ProvisionPlan;
188
203
  export declare function localBootstrapHost(): BootstrapHost;
204
+ export {};
package/dist/bootstrap.js CHANGED
@@ -1396,7 +1396,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1396
1396
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1397
1397
 
1398
1398
  // src/version.ts
1399
- var VERSION = "0.1.63";
1399
+ var VERSION = "0.1.64";
1400
1400
 
1401
1401
  // src/software.ts
1402
1402
  var PINNED_BUN_VERSION = "1.3.14";
@@ -2045,17 +2045,16 @@ function agentUnit(options) {
2045
2045
  if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
2046
2046
  throw new Error("migration pull and lifecycle profile must be supplied together");
2047
2047
  }
2048
- const bootstrapEnabled = Boolean(options.pullBootstrap && options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath && options.bootstrapTargetTelemetryEndpoint);
2049
- if ([
2050
- options.pullBootstrap,
2051
- options.bootstrapSshCredentialPath,
2052
- options.bootstrapSshPublicKeyPath,
2053
- options.bootstrapTargetTelemetryEndpoint
2054
- ].some(Boolean) && !bootstrapEnabled) {
2055
- throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
2048
+ const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
2049
+ if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath)) {
2050
+ throw new Error("bootstrap SSH credential and public key paths must be supplied together");
2051
+ }
2052
+ const bootstrapEnabled = Boolean(options.pullBootstrap);
2053
+ if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
2054
+ throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
2056
2055
  }
2057
2056
  const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
2058
- const bootstrapSshCredentialPath = bootstrapEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
2057
+ const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
2059
2058
  const warpValues = [
2060
2059
  options.warpOrganization,
2061
2060
  options.warpClientIdCredentialPath,
@@ -2294,15 +2293,13 @@ function planProvision(options) {
2294
2293
  if (Boolean(options.pullMigrations) !== Boolean(options.lifecycleProfilePath)) {
2295
2294
  throw new Error("migration pull and lifecycle profile must be supplied together");
2296
2295
  }
2297
- const bootstrapEnabled = Boolean(options.pullBootstrap && options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath && options.bootstrapTargetTelemetryEndpoint);
2298
- if ([
2299
- options.pullBootstrap,
2300
- options.bootstrapSshCredentialPath,
2301
- options.bootstrapSshPublicKeyPath,
2302
- options.bootstrapSshSourcePath,
2303
- options.bootstrapTargetTelemetryEndpoint
2304
- ].some(Boolean) && !bootstrapEnabled) {
2305
- throw new Error("bootstrap pull, SSH credential, public key and target telemetry endpoint must be supplied together");
2296
+ const bootstrapIdentityEnabled = Boolean(options.bootstrapSshCredentialPath && options.bootstrapSshPublicKeyPath);
2297
+ if (Boolean(options.bootstrapSshCredentialPath) !== Boolean(options.bootstrapSshPublicKeyPath) || options.bootstrapSshSourcePath && !bootstrapIdentityEnabled) {
2298
+ throw new Error("bootstrap SSH credential and public key paths must be supplied together");
2299
+ }
2300
+ const bootstrapEnabled = Boolean(options.pullBootstrap);
2301
+ if (bootstrapEnabled && (!bootstrapIdentityEnabled || !options.bootstrapTargetTelemetryEndpoint) || !bootstrapEnabled && options.bootstrapTargetTelemetryEndpoint) {
2302
+ throw new Error("bootstrap pull, SSH identity and target telemetry endpoint must be supplied together");
2306
2303
  }
2307
2304
  const warpValues = [
2308
2305
  options.warpOrganization,
@@ -2312,13 +2309,13 @@ function planProvision(options) {
2312
2309
  const warpEnabled = warpValues.every(Boolean);
2313
2310
  if (warpValues.some(Boolean) && !warpEnabled)
2314
2311
  throw new Error("WARP configuration must be supplied together");
2315
- const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath && options.enrolStatePath);
2316
- if (Boolean(options.enrolTokenCredentialPath) !== Boolean(options.enrolStatePath) || options.enrolTokenSourcePath && !enrolmentEnabled) {
2317
- throw new Error("direct enrolment credential and state paths must be supplied together");
2312
+ const enrolmentEnabled = Boolean(options.enrolTokenCredentialPath);
2313
+ if (options.enrolTokenCredentialPath && !options.enrolStatePath || options.enrolTokenSourcePath && (!options.enrolTokenCredentialPath || !options.enrolStatePath)) {
2314
+ throw new Error("direct enrolment credential requires its durable state path");
2318
2315
  }
2319
2316
  const enrolTokenSourcePath = options.enrolTokenSourcePath ? systemdPath(options.enrolTokenSourcePath, "enrolment source") : undefined;
2320
2317
  const enrolTokenCredentialPath = enrolmentEnabled ? systemdPath(options.enrolTokenCredentialPath, "enrolment credential") : undefined;
2321
- const enrolStatePath = enrolmentEnabled ? systemdPath(options.enrolStatePath, "enrolment state") : undefined;
2318
+ const enrolStatePath = options.enrolStatePath ? systemdPath(options.enrolStatePath, "enrolment state") : undefined;
2322
2319
  const enrolStateDir = enrolStatePath?.replace(/\/[^/]+$/, "");
2323
2320
  const sourceBinPath = options.sourceBinPath ? systemdPath(options.sourceBinPath, "agent source binary") : undefined;
2324
2321
  const binPath = options.binPath ? systemdPath(options.binPath, "agent binary") : undefined;
@@ -2330,8 +2327,8 @@ function planProvision(options) {
2330
2327
  const gitPublicKeyDir = gitPublicKeyPath?.replace(/\/[^/]+$/, "");
2331
2328
  const lifecycleProfilePath = lifecycleEnabled ? systemdPath(options.lifecycleProfilePath, "lifecycle profile") : undefined;
2332
2329
  const lifecycleHelperSocketPath = lifecycleEnabled ? systemdPath(options.lifecycleHelperSocketPath ?? LIFECYCLE_HELPER_SOCKET, "lifecycle helper socket") : undefined;
2333
- const bootstrapSshCredentialPath = bootstrapEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
2334
- const bootstrapSshPublicKeyPath = bootstrapEnabled ? systemdPath(options.bootstrapSshPublicKeyPath, "bootstrap SSH public key") : undefined;
2330
+ const bootstrapSshCredentialPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshCredentialPath, "bootstrap SSH credential") : undefined;
2331
+ const bootstrapSshPublicKeyPath = bootstrapIdentityEnabled ? systemdPath(options.bootstrapSshPublicKeyPath, "bootstrap SSH public key") : undefined;
2335
2332
  const bootstrapSshSourcePath = options.bootstrapSshSourcePath ? systemdPath(options.bootstrapSshSourcePath, "bootstrap SSH private-key source") : undefined;
2336
2333
  const bootstrapSshPublicKeyDir = bootstrapSshPublicKeyPath?.replace(/\/[^/]+$/, "");
2337
2334
  const warpClientIdCredentialPath = warpEnabled ? systemdPath(options.warpClientIdCredentialPath, "WARP client-id credential") : undefined;
@@ -2431,7 +2428,7 @@ function planProvision(options) {
2431
2428
  step("Git deploy identity directory", { kind: "directories", directories: [{ path: gitPublicKeyDir, mode: 493, owner: "root", group: "root" }] }),
2432
2429
  step("unique encrypted Git deploy identity", { kind: "ensure-git-identity", credential: gitCredentialPath, publicKey: gitPublicKeyPath })
2433
2430
  ] : [],
2434
- ...bootstrapEnabled ? [
2431
+ ...bootstrapIdentityEnabled ? [
2435
2432
  step("bootstrap SSH public identity directory", { kind: "directories", directories: [
2436
2433
  { path: bootstrapSshPublicKeyDir, mode: 493, owner: "root", group: "root" }
2437
2434
  ] }),
@@ -2467,6 +2464,10 @@ function planProvision(options) {
2467
2464
  { argv: ["/usr/bin/rm", "-f", "/etc/systemd/system/forgezero-deploy-runner.socket"] },
2468
2465
  { argv: ["/usr/bin/systemctl", "daemon-reload"] }
2469
2466
  ] })] : [],
2467
+ ...enrolStatePath && !enrolmentEnabled ? [step("retire consumed enrolment unit", { kind: "commands", commands: [
2468
+ { argv: ["/usr/bin/systemctl", "disable", "--now", "forgezero-agent-enrol.service"], acceptedExitCodes: [0, 1, 5] },
2469
+ { argv: ["/usr/bin/rm", "-f", ENROLMENT_UNIT_PATH] }
2470
+ ] })] : [],
2470
2471
  step("enable and converge services", { kind: "commands", commands: [
2471
2472
  { argv: ["/usr/bin/systemctl", "enable", ...enabledUnits] },
2472
2473
  { argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-agent.service"], acceptedExitCodes: [0, 1] },
@@ -4181,7 +4182,15 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4181
4182
  if (connectorCapabilities?.meshConnectorToken && !host.exists(WARP_CONNECTOR_CREDENTIAL)) {
4182
4183
  await seal(host, "CF_WARP_CONNECTOR_TOKEN", WARP_CONNECTOR_CREDENTIAL, connectorCapabilities.meshConnectorToken);
4183
4184
  }
4184
- await host.installAgent(config);
4185
+ if (alreadyEnrolled) {
4186
+ await host.installAgent(config, "bound");
4187
+ } else if (config.kind === "platform") {
4188
+ if (bootstrapManifest)
4189
+ await host.installAgent(config, "bootstrap");
4190
+ } else {
4191
+ await host.installAgent(config, "enrol");
4192
+ await host.installAgent(config, "bound");
4193
+ }
4185
4194
  if (config.kind === "platform" && config.firewall.enabled) {
4186
4195
  await host.ensureSoftware(plan.software.filter(({ id }) => id === "ufw"));
4187
4196
  } else
@@ -4331,7 +4340,19 @@ async function applyBootstrap(input, host = localBootstrapHost(), secrets, depen
4331
4340
  `, 384);
4332
4341
  host.remove(config.bootstrapBundle.bundleFile);
4333
4342
  host.remove(config.bootstrapBundle.manifestFile);
4334
- await host.installAgent(config);
4343
+ }
4344
+ if (!alreadyEnrolled) {
4345
+ await checked3(host, [
4346
+ "curl",
4347
+ "--fail",
4348
+ "--silent",
4349
+ "--show-error",
4350
+ "--max-time",
4351
+ "10",
4352
+ `http://127.0.0.1:3000${runtime.healthPath}`
4353
+ ], "release-one API health");
4354
+ await host.installAgent(config, "enrol");
4355
+ await host.installAgent(config, "bound");
4335
4356
  }
4336
4357
  }
4337
4358
  if (config.cloudflareHandoff) {
@@ -4500,6 +4521,65 @@ function readBootstrapConfig(path) {
4500
4521
  }
4501
4522
  return validateBootstrapConfig(strictBootstrapDocument(JSON.parse(readFileSync5(path, "utf8"))));
4502
4523
  }
4524
+ function planBootstrapAgentInstall(config, phase, context) {
4525
+ const { capabilities, hasBinding, hasEnrolCredential, initialBundle } = context;
4526
+ if (phase === "bootstrap") {
4527
+ if (config.kind !== "platform" || hasBinding || !initialBundle) {
4528
+ throw new Error("release-one Agent install requires an unbound platform host and verified bootstrap bundle");
4529
+ }
4530
+ } else if (phase === "enrol") {
4531
+ if (hasBinding || !hasEnrolCredential) {
4532
+ throw new Error("Agent enrolment requires one unbound host and its sealed one-use credential");
4533
+ }
4534
+ } else if (!hasBinding) {
4535
+ throw new Error("bound Agent convergence requires durable enrolment state");
4536
+ }
4537
+ const bound = phase === "bound";
4538
+ const enrolling = phase === "enrol";
4539
+ const authenticated = enrolling || bound;
4540
+ const bootstrapRunner = platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner);
4541
+ const options = {
4542
+ capabilities,
4543
+ socketPath: DEFAULT_SOCKET2,
4544
+ seedPath: "/var/lib/forgezero/node.seed",
4545
+ controlSocketPath: CONTROL_SOCKET,
4546
+ repository: phase === "bootstrap" ? initialBundle.path : bound && config.kind === "enrolled-compute" ? config.repository : undefined,
4547
+ branch: phase === "bootstrap" ? initialBundle.manifest.branch : bound && config.kind === "enrolled-compute" ? config.branch : undefined,
4548
+ bootstrapBundlePath: phase === "bootstrap" ? initialBundle.path : undefined,
4549
+ bootstrapBundleManifestPath: phase === "bootstrap" ? initialBundle.manifestPath : undefined,
4550
+ profile: config.profile,
4551
+ deployRoot: config.deployRoot ?? "/opt/forgezero",
4552
+ deploymentCredentials: config.deploymentCredentials,
4553
+ publicApiUrl: config.apiUrl,
4554
+ gitCredentialPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
4555
+ gitPublicKeyPath: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
4556
+ generateGitIdentity: authenticated && config.kind === "enrolled-compute" && config.gitDeployKey === true,
4557
+ pullDeployments: bound,
4558
+ pullMigrations: bound && config.kind === "platform",
4559
+ pullBootstrap: bound && bootstrapRunner,
4560
+ bootstrapSshCredentialPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
4561
+ bootstrapSshSourcePath: enrolling && config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
4562
+ bootstrapSshPublicKeyPath: authenticated && bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
4563
+ bootstrapTargetTelemetryEndpoint: bound && bootstrapRunner ? platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined : undefined,
4564
+ lifecycleProfilePath: bound && config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
4565
+ enforceEgress: true,
4566
+ nodeHostname: config.nodeHostname,
4567
+ telemetryEndpoint: config.telemetryEndpoint,
4568
+ binPath: "/usr/local/lib/forgezero/agent/fz-agent",
4569
+ sourceBinPath: PACKAGED_AGENT_BIN,
4570
+ ...enrolling ? {
4571
+ enrolTokenCredentialPath: ENROL_CREDENTIAL,
4572
+ enrolStatePath: "/var/lib/forgezero/enrolment.json"
4573
+ } : bound ? { enrolStatePath: "/var/lib/forgezero/enrolment.json" } : {},
4574
+ ...authenticated ? {
4575
+ apiUrl: config.apiUrl,
4576
+ project: config.kind === "enrolled-compute" ? config.realm : "platform",
4577
+ environment: config.kind === "enrolled-compute" ? undefined : config.environment,
4578
+ nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
4579
+ } : {}
4580
+ };
4581
+ return planInstall(options);
4582
+ }
4503
4583
  function localBootstrapHost() {
4504
4584
  const execute = async (argv, options = {}) => {
4505
4585
  const child = Bun.spawn([...argv], { stdin: options.stdin === undefined ? "ignore" : "pipe", stdout: "pipe", stderr: "pipe" });
@@ -4547,10 +4627,10 @@ function localBootstrapHost() {
4547
4627
  throw new Error(`Agent software requirements failed: ${result.output.trim()}`);
4548
4628
  return result;
4549
4629
  },
4550
- async installAgent(config) {
4630
+ async installAgent(config, phase) {
4551
4631
  const capabilities = await readCapabilities(localRunner);
4552
- const deployRoot = config.deployRoot ?? "/opt/forgezero";
4553
4632
  const hasBinding = existsSync5("/var/lib/forgezero/enrolment.json");
4633
+ const hasEnrolCredential = existsSync5(ENROL_CREDENTIAL);
4554
4634
  const initialBundle = config.kind === "platform" && !existsSync5(BOOTSTRAP_RELEASE_EVIDENCE) ? {
4555
4635
  path: config.bootstrapBundle.bundleFile,
4556
4636
  manifestPath: config.bootstrapBundle.manifestFile,
@@ -4571,45 +4651,11 @@ function localBootstrapHost() {
4571
4651
  writeFileSync5(LIFECYCLE_PROFILE, `${JSON.stringify(lifecycle, null, 2)}
4572
4652
  `, { mode: 256 });
4573
4653
  }
4574
- const plan = planInstall({
4654
+ const plan = planBootstrapAgentInstall(config, phase, {
4575
4655
  capabilities,
4576
- socketPath: DEFAULT_SOCKET2,
4577
- seedPath: "/var/lib/forgezero/node.seed",
4578
- controlSocketPath: "/run/forgezero/control.sock",
4579
- repository: initialBundle?.path ?? (config.kind === "enrolled-compute" ? config.repository : undefined),
4580
- branch: initialBundle?.manifest.branch ?? (config.kind === "enrolled-compute" ? config.branch : undefined),
4581
- bootstrapBundlePath: initialBundle?.path,
4582
- bootstrapBundleManifestPath: initialBundle?.manifestPath,
4583
- profile: config.kind === "platform" ? config.profile : config.profile,
4584
- deployRoot,
4585
- deploymentCredentials: config.deploymentCredentials,
4586
- publicApiUrl: config.apiUrl,
4587
- gitCredentialPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/creds/git-deploy-key.cred" : undefined,
4588
- gitPublicKeyPath: config.kind === "enrolled-compute" && config.gitDeployKey ? "/etc/forgezero/git/deploy.pub" : undefined,
4589
- generateGitIdentity: config.kind === "enrolled-compute" && config.gitDeployKey === true,
4590
- pullDeployments: hasBinding,
4591
- pullMigrations: config.kind === "platform" && hasBinding,
4592
- pullBootstrap: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && Boolean(config.bootstrapRunner),
4593
- bootstrapSshCredentialPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_CREDENTIAL : undefined,
4594
- bootstrapSshSourcePath: config.kind === "enrolled-compute" ? config.bootstrapRunner?.sshPrivateKeyFile : undefined,
4595
- bootstrapSshPublicKeyPath: platformBootstrapRunner(config) || config.kind === "enrolled-compute" && config.bootstrapRunner ? BOOTSTRAP_SSH_PUBLIC_KEY : undefined,
4596
- bootstrapTargetTelemetryEndpoint: platformBootstrapRunner(config) ? config.telemetryEndpoint : config.kind === "enrolled-compute" ? config.bootstrapRunner?.targetTelemetryEndpoint : undefined,
4597
- lifecycleProfilePath: config.kind === "platform" ? LIFECYCLE_PROFILE : undefined,
4598
- enforceEgress: true,
4599
- nodeHostname: config.nodeHostname,
4600
- telemetryEndpoint: config.telemetryEndpoint,
4601
- binPath: "/usr/local/lib/forgezero/agent/fz-agent",
4602
- sourceBinPath: PACKAGED_AGENT_BIN,
4603
- ...existsSync5(ENROL_CREDENTIAL) || hasBinding ? {
4604
- enrolTokenCredentialPath: existsSync5(ENROL_CREDENTIAL) ? ENROL_CREDENTIAL : undefined,
4605
- enrolStatePath: "/var/lib/forgezero/enrolment.json"
4606
- } : {},
4607
- ...hasBinding ? {
4608
- apiUrl: config.apiUrl,
4609
- project: config.kind === "enrolled-compute" ? config.realm : "platform",
4610
- environment: config.kind === "enrolled-compute" ? undefined : config.environment,
4611
- nodeLabel: config.kind === "enrolled-compute" ? config.nodeHostname : config.computeReference
4612
- } : {}
4656
+ hasBinding,
4657
+ hasEnrolCredential,
4658
+ initialBundle
4613
4659
  });
4614
4660
  for (const unit of [{ path: plan.unitPath, unit: plan.unit }, ...plan.auxiliaryUnits]) {
4615
4661
  mkdirSync5(dirname6(unit.path), { recursive: true, mode: 493 });
@@ -4626,6 +4672,7 @@ export {
4626
4672
  validateBootstrapConfig,
4627
4673
  resolveInstalledBootstrapKind,
4628
4674
  readBootstrapConfig,
4675
+ planBootstrapAgentInstall,
4629
4676
  planBootstrap,
4630
4677
  localBootstrapHost,
4631
4678
  bootstrapStatus,
@@ -34,6 +34,8 @@ export interface DeploymentResult {
34
34
  definitionDigest: string;
35
35
  definitionVersion: number;
36
36
  profile: string;
37
+ /** Resolved only from the validated plan and its bounded inputs at this revision. */
38
+ resolvedTargets?: readonly ResolvedDeploymentTarget[];
37
39
  release: string;
38
40
  ok: boolean;
39
41
  phases: readonly RunResult[];
@@ -44,6 +46,15 @@ export interface DeploymentResult {
44
46
  };
45
47
  };
46
48
  }
49
+ export interface ResolvedDeploymentTarget {
50
+ name: string;
51
+ profiles: readonly string[];
52
+ confidentialCompute: 'required' | 'preferred' | 'disabled';
53
+ labels: Readonly<Record<string, string>>;
54
+ minimum: number;
55
+ desired: number;
56
+ maximum: number;
57
+ }
47
58
  /**
48
59
  * How the credential-bearing agent may read one server-owned Git source.
49
60
  * Secret values never appear in a deployment claim or checked-in pipeline.